[NCC] Implement several cards (#9328)

Many associated refactors too. See full PR for detail.
This commit is contained in:
Alex Vasile 2022-09-22 21:38:29 -04:00 committed by GitHub
parent b7151cfa58
commit fd16f2a16b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
104 changed files with 6091 additions and 1069 deletions

View file

@ -18,7 +18,7 @@ public final class ALittleChat extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
// Casualty 1
this.addAbility(new CasualtyAbility(this, 1));
this.addAbility(new CasualtyAbility(1));
// Look at the top two cards of your library. Put one of them into your hand and the other on the bottom of your library.
this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect(2, 1, PutCards.HAND, PutCards.BOTTOM_ANY));

View file

@ -0,0 +1,93 @@
package mage.cards.a;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SpellCastOpponentTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.meta.OrTriggeredAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetNonlandPermanent;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class AerialExtortionist extends CardImpl {
public AerialExtortionist(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}");
this.subtype.add(SubType.BIRD);
this.subtype.add(SubType.SOLDIER);
this.power = new MageInt(4);
this.toughness = new MageInt(3);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Whenever Aerial Extortionist enters the battlefield or deals combat damage to a player, exile up to one target nonland permanent.
// For as long as that card remains exiled, its owner may cast it.
Ability exileAbility = new OrTriggeredAbility(
Zone.BATTLEFIELD,
new AerialExtortionistExileEffect(),
false,
"Whenever Aerial Extortionist enters the battlefield or deals combat damage to a player, ",
new EntersBattlefieldTriggeredAbility(null, false),
new DealsCombatDamageToAPlayerTriggeredAbility(null, false)
);
exileAbility.addTarget(new TargetNonlandPermanent());
this.addAbility(exileAbility);
// Whenever another player casts a spell from anywhere other than their hand, draw a card.
this.addAbility(new SpellCastOpponentTriggeredAbility(new DrawCardSourceControllerEffect(1), false, true));
}
private AerialExtortionist(final AerialExtortionist card) {
super(card);
}
@Override
public AerialExtortionist copy() {
return new AerialExtortionist(this);
}
}
class AerialExtortionistExileEffect extends OneShotEffect {
public AerialExtortionistExileEffect() {
super(Outcome.Benefit);
this.staticText = "exile up to one target nonland permanent. " +
"For as long as that card remains exiled, its owner may cast it";
}
public AerialExtortionistExileEffect(final AerialExtortionistExileEffect effect) {
super(effect);
}
@Override
public AerialExtortionistExileEffect copy() {
return new AerialExtortionistExileEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (controller == null || targetPermanent == null) {
return false;
}
return PlayFromNotOwnHandZoneTargetEffect.exileAndPlayFromExile(game, source, targetPermanent,
TargetController.OWNER, Duration.Custom, false, false, true);
}
}

View file

@ -0,0 +1,119 @@
package mage.cards.a;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.common.SacrificeSourceCost;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.GainLifeEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.Set;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class AgentsToolkit extends CardImpl {
public AgentsToolkit(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{G}{U}");
this.subtype.add(SubType.CLUE);
// Agents Toolkit enters the battlefield with a +1/+1 counter, a flying counter, a deathtouch counter, and a shield counter on it.
Ability counterETBAbility = new EntersBattlefieldAbility(new AddCountersSourceEffect(CounterType.P1P1.createInstance(1)).setText("with a +1/+1 counter"));
counterETBAbility.addEffect(new AddCountersSourceEffect(CounterType.FLYING.createInstance(1)).setText("a flying counter").concatBy(", "));
counterETBAbility.addEffect(new AddCountersSourceEffect(CounterType.DEATHTOUCH.createInstance(1)).setText("a deathtouch counter").concatBy(", "));
counterETBAbility.addEffect(new AddCountersSourceEffect(CounterType.SHIELD.createInstance(1)).setText("and a shield counter on it").concatBy(", "));
this.addAbility(counterETBAbility);
// Whenever a creature enters the battlefield under your control,
// you may move a counter from Agents Toolkit onto that creature.
this.addAbility(new EntersBattlefieldControlledTriggeredAbility(
new AgentToolkitMoveCounterEffect(),
StaticFilters.FILTER_PERMANENT_CREATURE)
);
// {2}, Sacrifice Agents Toolkit: Draw a card.
Ability drawAbility = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), new GenericManaCost(2));
drawAbility.addCost(new SacrificeSourceCost());
this.addAbility(drawAbility);
}
private AgentsToolkit(final AgentsToolkit card) {
super(card);
}
@Override
public AgentsToolkit copy() {
return new AgentsToolkit(this);
}
}
class AgentToolkitMoveCounterEffect extends OneShotEffect {
public AgentToolkitMoveCounterEffect() {
super(Outcome.Benefit);
this.staticText = "you may move a counter from Agent's Toolkit onto that creature";
}
private AgentToolkitMoveCounterEffect(final AgentToolkitMoveCounterEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent agentsToolkitPermanent = game.getPermanent(source.getSourceId());
Player controller = game.getPlayer(source.getControllerId());
if (agentsToolkitPermanent == null || controller == null) {
return false;
}
Object enteringObject = this.getValue("permanentEnteringBattlefield");
if (!(enteringObject instanceof Permanent)) {
return false;
}
Permanent enteringCreature = (Permanent) enteringObject;
Choice moveCounterChoice = new ChoiceImpl(false);
Set<String> possibleCounterNames = agentsToolkitPermanent.getCounters(game).keySet();
moveCounterChoice.setMessage("Choose counter to move");
moveCounterChoice.setChoices(possibleCounterNames);
if (controller.choose(outcome, moveCounterChoice, game) && possibleCounterNames.contains(moveCounterChoice.getChoice())) {
String counterName = moveCounterChoice.getChoice();
CounterType counterType = CounterType.findByName(counterName);
if (counterType == null) {
return false;
}
agentsToolkitPermanent.removeCounters(counterType.getName(), 1, source, game);
enteringCreature.addCounters(counterType.createInstance(), source, game);
}
return true;
}
@Override
public AgentToolkitMoveCounterEffect copy() {
return new AgentToolkitMoveCounterEffect(this);
}
}

View file

@ -1,24 +1,18 @@
package mage.cards.a;
import java.util.UUID;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.TargetOfOpponentsSpellOrAbilityTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CounterUnlessPaysEffect;
import mage.abilities.effects.common.continuous.BoostAllEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.filter.FilterStackObject;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.StackObject;
import mage.target.targetpointer.FixedTarget;
/**
*
@ -30,7 +24,7 @@ public final class AmuletOfSafekeeping extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}");
// Whenever you become the target of a spell or ability an opponent controls, counter that spell or ability unless its controller pays {1}.
this.addAbility(new AmuletOfSafekeepingTriggeredAbility());
this.addAbility(new TargetOfOpponentsSpellOrAbilityTriggeredAbility(new CounterUnlessPaysEffect(new GenericManaCost(1)), Boolean.TRUE));
// Creature tokens get -1/-0.
this.addAbility(new SimpleStaticAbility(
@ -51,49 +45,3 @@ public final class AmuletOfSafekeeping extends CardImpl {
return new AmuletOfSafekeeping(this);
}
}
class AmuletOfSafekeepingTriggeredAbility extends TriggeredAbilityImpl {
private static final FilterStackObject filter = new FilterStackObject();
static {
filter.add(TargetController.OPPONENT.getControllerPredicate());
}
public AmuletOfSafekeepingTriggeredAbility() {
super(Zone.BATTLEFIELD, new CounterUnlessPaysEffect(new GenericManaCost(1)));
}
public AmuletOfSafekeepingTriggeredAbility(final AmuletOfSafekeepingTriggeredAbility ability) {
super(ability);
}
@Override
public AmuletOfSafekeepingTriggeredAbility copy() {
return new AmuletOfSafekeepingTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.TARGETED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
StackObject stackObject = game.getStack().getStackObject(event.getSourceId());
if (event.getTargetId().equals(getControllerId())
&& filter.match(stackObject, getControllerId(), this, game)) {
for (Effect effect : this.getEffects()) {
effect.setTargetPointer(new FixedTarget(stackObject.getId(), game));
}
return true;
}
return false;
}
@Override
public String getRule() {
return "Whenever you become the target of a spell or ability an opponent controls, "
+ "counter that spell or ability unless its controller pays {1}";
}
}

View file

@ -0,0 +1,140 @@
package mage.cards.a;
import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.keyword.CasualtyAbility;
import mage.abilities.keyword.DeathtouchAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.watchers.Watcher;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class AnheloThePainter extends CardImpl {
public AnheloThePainter(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{U}{B}{R}");
this.addSuperType(SuperType.LEGENDARY);
this.addSubType(SubType.VAMPIRE, SubType.ASSASSIN);
this.power = new MageInt(1);
this.toughness = new MageInt(3);
// Deathtouch
this.addAbility(DeathtouchAbility.getInstance());
// The first instant or sorcery spell you cast each turn has casualty 2.
this.addAbility(
new SimpleStaticAbility(
Zone.BATTLEFIELD,
new AnheloThePainterGainCausalityEffect()),
new AnheloThePainterWatcher()
);
}
private AnheloThePainter(final AnheloThePainter card) {
super(card);
}
@Override
public AnheloThePainter copy() {
return new AnheloThePainter(this);
}
}
class AnheloThePainterGainCausalityEffect extends ContinuousEffectImpl {
AnheloThePainterGainCausalityEffect() {
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
staticText = "The first instant or sorcery spell you cast each turn has casualty 2. " +
"<i>(As you cast that spell, you may sacrifice a creature with power 2 or greater. " +
"When you do, copy the spell and you may choose new targets for the copy.)</i>";
}
AnheloThePainterGainCausalityEffect(final AnheloThePainterGainCausalityEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
AnheloThePainterWatcher watcher = game.getState().getWatcher(AnheloThePainterWatcher.class);
if (controller == null || watcher == null) {
return false;
}
boolean applied = false;
for (StackObject stackObject : game.getStack()) {
if (!(stackObject instanceof Spell)
|| stackObject.isCopy()
|| !stackObject.isControlledBy(source.getControllerId())
|| !AnheloThePainterWatcher.checkSpell(stackObject, game) ) {
continue;
}
Spell spell = (Spell) stackObject;
Card card = spell.getCard();
game.getState().addOtherAbility(card, new CasualtyAbility(2));
applied = true;
}
return applied;
}
@Override
public AnheloThePainterGainCausalityEffect copy() {
return new AnheloThePainterGainCausalityEffect(this);
}
}
class AnheloThePainterWatcher extends Watcher {
private final Map<UUID, MageObjectReference> playerMap = new HashMap<>();
AnheloThePainterWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.CAST_SPELL) {
return;
}
Spell spell = game.getSpell(event.getSourceId());
if (spell == null || !spell.isInstantOrSorcery(game)) {
return;
}
playerMap.computeIfAbsent(event.getPlayerId(), x -> new MageObjectReference(spell.getMainCard(), game));
}
@Override
public void reset() {
playerMap.clear();
super.reset();
}
static boolean checkSpell(StackObject stackObject, Game game) {
if (stackObject.isCopy() || !(stackObject instanceof Spell)) {
return false;
}
UUID controllerId = stackObject.getControllerId();
AnheloThePainterWatcher watcher = game.getState().getWatcher(AnheloThePainterWatcher.class);
return watcher.playerMap.containsKey(controllerId)
&& watcher.playerMap.get(controllerId).refersTo(((Spell) stackObject).getMainCard(), game);
}
}

View file

@ -34,7 +34,7 @@ public final class AudaciousSwap extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{R}");
// Casualty 2
this.addAbility(new CasualtyAbility(this, 2));
this.addAbility(new CasualtyAbility(2));
// The owner of target nonenchantment permanent shuffles it into their library, then exiles the top card of their library. If it's a land card, they put it onto the battlefield. Otherwise, they may cast it without paying its mana cost.
this.getSpellAbility().addEffect(new AudaciousSwapEffect());

View file

@ -3,14 +3,11 @@ package mage.cards.b;
import java.util.UUID;
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.filter.FilterCard;
import mage.filter.common.FilterControlledLandPermanent;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.filter.predicate.card.CardManaCostLessThanControlledLandCountPredicate;
import mage.target.common.TargetCardInLibrary;
/**
@ -20,9 +17,10 @@ import mage.target.common.TargetCardInLibrary;
public final class BeseechTheQueen extends CardImpl {
private static final FilterCard filter = new FilterCard("card with mana value less than or equal to the number of lands you control");
static{
filter.add(new BeseechTheQueenPredicate());
static {
filter.add(CardManaCostLessThanControlledLandCountPredicate.getInstance());
}
public BeseechTheQueen(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2/B}{2/B}{2/B}");
@ -41,21 +39,3 @@ public final class BeseechTheQueen extends CardImpl {
return new BeseechTheQueen(this);
}
}
class BeseechTheQueenPredicate implements Predicate<Card> {
@Override
public final boolean apply(Card input, Game game) {
if(input.getManaValue() <= game.getBattlefield().getAllActivePermanents(new FilterControlledLandPermanent(), input.getOwnerId(), game).size()){
return true;
}
return false;
}
@Override
public String toString() {
return "card with mana value less than or equal to the number of lands you control";
}
}

View file

@ -0,0 +1,68 @@
package mage.cards.b;
import mage.MageInt;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.EntersBattlefieldOneOrMoreTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.dynamicvalue.common.CountersSourceCount;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.mageobject.*;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class BessSoulNourisher extends CardImpl {
static final FilterCreaturePermanent filter = new FilterCreaturePermanent("one or more other creatures with base power and toughness 1/1");
static {
filter.add(AnotherPredicate.instance);
filter.add(new BasePowerPredicate(ComparisonType.EQUAL_TO, 1));
filter.add(new BaseToughnessPredicate(ComparisonType.EQUAL_TO, 1));
}
public BessSoulNourisher(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{W}");
this.addSuperType(SuperType.LEGENDARY);
this.addSubType(SubType.HUMAN, SubType.CITIZEN);
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// Whenever one or more other creatures with base power and toughness 1/1 enter the battlefield under your control,
// put a +1/+1 counter on Bess, Soul Nourisher.
this.addAbility(new EntersBattlefieldOneOrMoreTriggeredAbility(
new AddCountersSourceEffect(CounterType.P1P1.createInstance()),
filter,
TargetController.YOU)
);
// Whenever Bess attacks, each other creature you control with base power and toughness 1/1 gets
// +X/+X until end of turn, where X is the number of +1/+1 counters on Bess.
DynamicValue xValue = new CountersSourceCount(CounterType.P1P1);
this.addAbility(new AttacksTriggeredAbility(
new BoostControlledEffect(xValue, xValue, Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURE, true),
false,
"whenever Bess attacks, each other creature you control with base power and toughness 1/1 " +
"gets +X/+X until end of turn, where X is the number of +1/+1 counters on Bess")
);
}
private BessSoulNourisher(final BessSoulNourisher card) {
super(card);
}
@Override
public BessSoulNourisher copy() {
return new BessSoulNourisher(this);
}
}

View file

@ -0,0 +1,107 @@
package mage.cards.c;
import mage.abilities.Ability;
import mage.abilities.common.DiesCreatureTriggeredAbility;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.asthought.PlayFromNotOwnHandZoneTargetEffect;
import mage.abilities.effects.keyword.ManifestEffect;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.card.FaceDownPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class CrypticPursuit extends CardImpl {
private static final FilterPermanent filterFaceDownPermanent = new FilterControlledCreaturePermanent("a face-down creature");
static {
filterFaceDownPermanent.add(FaceDownPredicate.instance);
}
public CrypticPursuit(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{R}");
// Whenever you cast an instant or sorcery spell from your hand, manifest the top card of your library.
// (Put that card onto the battlefield face down as a 2/2 creature.
// Turn it face up any time for its mana cost if its a creature card.)
this.addAbility(new SpellCastControllerTriggeredAbility(
new ManifestEffect(1),
StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY,
false,
Zone.HAND)
);
// Whenever a face-down creature you control dies, exile it if its an instant or sorcery card.
// You may cast that card until the end of your next turn.
this.addAbility(new DiesCreatureTriggeredAbility(
new CrypticPursuitExileAndPlayEffect(),
false,
filterFaceDownPermanent)
);
}
private CrypticPursuit(final CrypticPursuit card) {
super(card);
}
@Override
public CrypticPursuit copy() {
return new CrypticPursuit(this);
}
}
class CrypticPursuitExileAndPlayEffect extends OneShotEffect {
CrypticPursuitExileAndPlayEffect() {
super(Outcome.Benefit);
this.staticText = "exile it if it's an instant or sorcery card. " +
"You may cast that card until the end of your next turn.";
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Object diedObject = getValue("creatureDied");
if (controller == null || !(diedObject instanceof Permanent)) {
return false;
}
Permanent diedFaceDownCreature = (Permanent) diedObject;
Card card = game.getCard(diedFaceDownCreature.getMainCard().getId());
if (card == null || !card.isInstantOrSorcery(game)) {
return false;
}
PlayFromNotOwnHandZoneTargetEffect.exileAndPlayFromExile(
game, source, card, TargetController.YOU,
Duration.UntilEndOfYourNextTurn,
false, false, true
);
return false;
}
CrypticPursuitExileAndPlayEffect(final CrypticPursuitExileAndPlayEffect effect) {
super(effect);
}
@Override
public Effect copy() {
return new CrypticPursuitExileAndPlayEffect(this);
}
}

View file

@ -19,7 +19,7 @@ public final class CutOfTheProfits extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{B}{B}");
// Casualty 3
this.addAbility(new CasualtyAbility(this, 3));
this.addAbility(new CasualtyAbility(3));
// You draw X cards and you lose X life.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(ManacostVariableValue.REGULAR, "you"));

View file

@ -18,7 +18,7 @@ public final class CutYourLosses extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{U}{U}");
// Casualty 2
this.addAbility(new CasualtyAbility(this, 2));
this.addAbility(new CasualtyAbility(2));
// Target player mills half their library, rounded down.
this.getSpellAbility().addEffect(new MillHalfLibraryTargetEffect(false));

View file

@ -4,22 +4,16 @@ import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.dynamicvalue.common.LandsYouControlCount;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.ExileTargetEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.effects.keyword.SurveilEffect;
import mage.abilities.hint.common.LandsYouControlHint;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInGraveyard;
import mage.target.common.TargetCardInHand;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
@ -52,7 +46,7 @@ public final class DakkonShadowSlayer extends CardImpl {
this.addAbility(ability);
// 6: You may put an artifact card from your hand or graveyard onto the battlefield.
this.addAbility(new LoyaltyAbility(new DakkonShadowSlayerEffect(), -6));
this.addAbility(new LoyaltyAbility(new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(StaticFilters.FILTER_CARD_ARTIFACT), -6));
}
private DakkonShadowSlayer(final DakkonShadowSlayer card) {
@ -64,45 +58,3 @@ public final class DakkonShadowSlayer extends CardImpl {
return new DakkonShadowSlayer(this);
}
}
class DakkonShadowSlayerEffect extends OneShotEffect {
DakkonShadowSlayerEffect() {
super(Outcome.Benefit);
staticText = "you may put an artifact card from your hand or graveyard onto the battlefield";
}
private DakkonShadowSlayerEffect(final DakkonShadowSlayerEffect effect) {
super(effect);
}
@Override
public DakkonShadowSlayerEffect copy() {
return new DakkonShadowSlayerEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
boolean inGrave = player.getGraveyard().count(StaticFilters.FILTER_CARD_ARTIFACT, game) > 0;
if (!inGrave && player.getHand().count(StaticFilters.FILTER_CARD_ARTIFACT, game) <1) {
return false;
}
TargetCard target;
if (!inGrave || player.chooseUse(
outcome, "Choose a card in your hand or your graveyard?",
null, "Hand", "Graveyard", source, game
)) {
target = new TargetCardInHand(0, 1, StaticFilters.FILTER_CARD_ARTIFACT);
player.choose(outcome, player.getHand(), target, game);
} else {
target = new TargetCardInGraveyard(0, 1, StaticFilters.FILTER_CARD_ARTIFACT);
player.choose(outcome, player.getGraveyard(), target, game);
}
Card card = game.getCard(target.getFirstTarget());
return card != null && player.moveCards(card, Zone.BATTLEFIELD, source, game);
}
}

View file

@ -0,0 +1,98 @@
package mage.cards.d;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.common.EntersBattlefieldControlledTriggeredAbility;
import mage.abilities.condition.common.SourceHasCountersCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.counter.AddCounterChoiceSourceEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class DenryKlinEditorInChief extends CardImpl {
public DenryKlinEditorInChief(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}{U}");
this.addSuperType(SuperType.LEGENDARY);
this.addSubType(SubType.CAT, SubType.ADVISOR);
this.power = new MageInt(2);
this.toughness = new MageInt(2);
// Denry Klin, Editor in Chief enters the battlefield with your choice of a +1/+1, first strike, or vigilance counter on it.
this.addAbility(new EntersBattlefieldAbility(
new AddCounterChoiceSourceEffect(CounterType.P1P1, CounterType.FIRST_STRIKE, CounterType.VIGILANCE)
));
// Whenever a nontoken creature enters the battlefield under your control,
// if Denry has counters on it, put the same number of each kind of counter on that creature.
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
new EntersBattlefieldControlledTriggeredAbility(
new DenryKlinEditorInChiefCopyCountersEffect(),
StaticFilters.FILTER_CONTROLLED_CREATURE_NON_TOKEN),
SourceHasCountersCondition.instance,
"Whenever a nontoken creature enters the battlefield under your control, " +
"if Denry has counters on it, put the same number of each kind of counter on that creature.")
);
}
private DenryKlinEditorInChief(final DenryKlinEditorInChief card) {
super(card);
}
@Override
public DenryKlinEditorInChief copy() {
return new DenryKlinEditorInChief(this);
}
}
class DenryKlinEditorInChiefCopyCountersEffect extends OneShotEffect {
DenryKlinEditorInChiefCopyCountersEffect() {
super(Outcome.Benefit);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent denryPermanent = game.getPermanent(source.getSourceId());
Object enteringObject = getValue("permanentEnteringBattlefield");
if (controller == null || denryPermanent == null || !(enteringObject instanceof Permanent)) {
return false;
}
Permanent enteringCreature = (Permanent) enteringObject;
for (Counter counter : denryPermanent.getCounters(game).values()) {
enteringCreature.addCounters(counter, source, game);
}
return true;
}
private DenryKlinEditorInChiefCopyCountersEffect(final DenryKlinEditorInChiefCopyCountersEffect effect) {
super(effect);
}
@Override
public Effect copy() {
return new DenryKlinEditorInChiefCopyCountersEffect(this);
}
}

View file

@ -26,7 +26,7 @@ public final class DigUpTheBody extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}");
// Casualty 1
this.addAbility(new CasualtyAbility(this, 1));
this.addAbility(new CasualtyAbility(1));
// Mill two cards, then return a creature card from your graveyard to your hand.
this.getSpellAbility().addEffect(new DigUpTheBodyEffect());

View file

@ -2,30 +2,23 @@
package mage.cards.d;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.common.continuous.EachSpellYouCastHasReplicateEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.ReplicateAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.common.FilterInstantOrSorcerySpell;
import mage.game.Game;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import mage.game.permanent.Permanent;
/**
*
* @author LevelX2
*/
public final class DjinnIlluminatus extends CardImpl {
private static final FilterInstantOrSorcerySpell filter = new FilterInstantOrSorcerySpell();
public DjinnIlluminatus(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{5}{U/R}{U/R}");
this.subtype.add(SubType.DJINN);
@ -36,8 +29,7 @@ public final class DjinnIlluminatus extends CardImpl {
// Flying
this.addAbility(FlyingAbility.getInstance());
// Each instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new DjinnIlluminatusGainReplicateEffect()));
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new EachSpellYouCastHasReplicateEffect(filter)));
}
private DjinnIlluminatus(final DjinnIlluminatus card) {
@ -49,51 +41,3 @@ public final class DjinnIlluminatus extends CardImpl {
return new DjinnIlluminatus(this);
}
}
class DjinnIlluminatusGainReplicateEffect extends ContinuousEffectImpl {
private static final FilterInstantOrSorcerySpell filter = new FilterInstantOrSorcerySpell();
private final Map<UUID, ReplicateAbility> replicateAbilities = new HashMap<>();
public DjinnIlluminatusGainReplicateEffect() {
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
staticText = "Each instant and sorcery spell you cast has replicate. The replicate cost is equal to its mana cost "
+ "<i>(When you cast it, copy it for each time you paid its replicate cost. You may choose new targets for the copies.)</i>";
}
public DjinnIlluminatusGainReplicateEffect(final DjinnIlluminatusGainReplicateEffect effect) {
super(effect);
this.replicateAbilities.putAll(effect.replicateAbilities);
}
@Override
public DjinnIlluminatusGainReplicateEffect copy() {
return new DjinnIlluminatusGainReplicateEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent djinn = game.getPermanent(source.getSourceId());
if (djinn == null) {
return false;
}
for (StackObject stackObject : game.getStack()) {
// only spells cast, so no copies of spells
if ((stackObject instanceof Spell)
&& !stackObject.isCopy()
&& stackObject.isControlledBy(source.getControllerId())
&& djinn.isControlledBy(source.getControllerId()) // verify that the controller of the djinn cast that spell
&& !stackObject.getManaCost().isEmpty()) { //handle cases like Ancestral Vision
Spell spell = (Spell) stackObject;
if (filter.match(stackObject, game)) {
ReplicateAbility replicateAbility = replicateAbilities.computeIfAbsent(spell.getId(), k -> new ReplicateAbility(spell.getSpellAbility().getManaCosts().getText()));
game.getState().addOtherAbility(spell.getCard(), replicateAbility, false); // Do not copy because paid and # of activations state is handled in the baility
}
}
}
if (game.getStack().isEmpty()) {
replicateAbilities.clear();
}
return true;
}
}

View file

@ -38,8 +38,8 @@ public class DonalHeraldOfWings extends CardImpl {
filterSpell.add(new AbilityPredicate(FlyingAbility.class));
}
public DonalHeraldOfWings(UUID ownderId, CardSetInfo setInfo) {
super(ownderId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}");
public DonalHeraldOfWings(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}");
this.addSuperType(SuperType.LEGENDARY);

View file

@ -0,0 +1,72 @@
package mage.cards.f;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.common.AttacksWithCreaturesTriggeredAbility;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.costs.common.RemoveCountersSourceCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetAttackingCreature;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class FamilysFavor extends CardImpl {
public FamilysFavor(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}");
// Whenever you attack, put a shield counter on target attacking creature.
// Until end of turn, it gains
// Whenever this creature deals combat damage to a player,
// remove a shield counter from it.
// If you do, draw a card.
// (If a creature with a shield counter on it would be dealt damage or destroyed, remove a shield counter from it instead.)
Ability attacksAbility = new AttacksWithCreaturesTriggeredAbility(new AddCountersTargetEffect(CounterType.SHIELD.createInstance()), 1);
attacksAbility.addEffect(new GainAbilityTargetEffect(
new DealsCombatDamageToAPlayerTriggeredAbility(
new DoIfCostPaid(
new DrawCardSourceControllerEffect(1),
new RemoveCountersSourceCost(CounterType.SHIELD.createInstance())),
false),
Duration.EndOfTurn,
"Until end of turn, it gains " +
"\"Whenever this creature deals combat damage to a player, remove a shield counter from it. " +
"If you do, draw a card.\""
).setText("Until end of turn, it gains " +
"\"Whenever this creature deals combat damage to a player, remove a shield counter from it. " +
"If you do, draw a card.\" " +
"<i>(If a creature with a shield counter on it would be dealt damage or destroyed, remove a shield counter from it instead.)</i>"
)
);
attacksAbility.addTarget(new TargetAttackingCreature());
this.addAbility(attacksAbility);
}
private FamilysFavor(final FamilysFavor card) {
super(card);
}
@Override
public FamilysFavor copy() {
return new FamilysFavor(this);
}
}

View file

@ -35,7 +35,7 @@ public final class FlawlessForgery extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{U}{U}");
// Casualty 3
this.addAbility(new CasualtyAbility(this, 3));
this.addAbility(new CasualtyAbility(3));
// Exile target instant or sorcery card from an opponent's graveyard. Copy that card. You may cast the copy without paying its mana cost.
this.getSpellAbility().addEffect(new FlawlessForgeryEffect());

View file

@ -8,6 +8,7 @@ import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbil
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.ReturnToBattlefieldAttachedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
@ -75,57 +76,25 @@ class GiftOfImmortalityEffect extends OneShotEffect {
public boolean apply(Game game, Ability source) {
Permanent enchantment = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD);
Player controller = game.getPlayer(source.getControllerId());
if (controller != null && enchantment != null && enchantment.getAttachedTo() != null) {
Permanent enchanted = (Permanent) game.getLastKnownInformation(enchantment.getAttachedTo(), Zone.BATTLEFIELD);
Card card = game.getCard(enchantment.getAttachedTo());
if (card != null && enchanted != null && card.getZoneChangeCounter(game) == enchanted.getZoneChangeCounter(game) + 1) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game, false, false, true, null);
Permanent permanent = game.getPermanent(card.getId());
if (permanent != null) {
//create delayed triggered ability
Effect effect = new GiftOfImmortalityReturnEnchantmentEffect();
effect.setTargetPointer(new FixedTarget(permanent, game));
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source);
}
}
return true;
if (controller == null || enchantment == null || enchantment.getAttachedTo() == null) {
return false;
}
Permanent enchanted = (Permanent) game.getLastKnownInformation(enchantment.getAttachedTo(), Zone.BATTLEFIELD);
Card card = game.getCard(enchantment.getAttachedTo());
if (card == null || enchanted == null || card.getZoneChangeCounter(game) != enchanted.getZoneChangeCounter(game) + 1) {
return false;
}
return false;
}
}
class GiftOfImmortalityReturnEnchantmentEffect extends OneShotEffect {
public GiftOfImmortalityReturnEnchantmentEffect() {
super(Outcome.PutCardInPlay);
staticText = "Return {this} to the battlefield attached to that creature at the beginning of the next end step";
}
public GiftOfImmortalityReturnEnchantmentEffect(final GiftOfImmortalityReturnEnchantmentEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Card aura = game.getCard(source.getSourceId());
if (aura != null && game.getState().getZone(aura.getId()) == Zone.GRAVEYARD) {
Player controller = game.getPlayer(source.getControllerId());
Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source));
if (controller != null && creature != null) {
game.getState().setValue("attachTo:" + aura.getId(), creature);
controller.moveCards(aura, Zone.BATTLEFIELD, source, game);
return creature.addAttachment(aura.getId(), source, game);
}
controller.moveCards(card, Zone.BATTLEFIELD, source, game, false, false, true, null);
Permanent permanent = game.getPermanent(card.getId());
if (permanent == null) {
return false;
}
return false;
}
@Override
public GiftOfImmortalityReturnEnchantmentEffect copy() {
return new GiftOfImmortalityReturnEnchantmentEffect(this);
// Create delayed triggered ability
Effect effect = new ReturnToBattlefieldAttachedEffect();
effect.setTargetPointer(new FixedTarget(permanent, game));
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source);
return true;
}
}

View file

@ -30,7 +30,7 @@ public final class GrislySigil extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}");
// Casualty 1
this.addAbility(new CasualtyAbility(this, 1));
this.addAbility(new CasualtyAbility(1));
// Choose target creature or planeswalker. If it was dealt noncombat damage this turn, Grisly Sigil deals 3 damage to it and you gain 3 life. Otherwise, Grisly Sigil deals 1 damage to it and you gain 1 life.
this.getSpellAbility().addEffect(new GrislySigilEffect());

View file

@ -18,7 +18,7 @@ public final class IllicitShipment extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{B}{B}");
// Casualty 3
this.addAbility(new CasualtyAbility(this, 3));
this.addAbility(new CasualtyAbility(3));
// Search your library for a card, put that card into your hand, then shuffle.
this.getSpellAbility().addEffect(new SearchLibraryPutInHandEffect(new TargetCardInLibrary()));

View file

@ -4,7 +4,7 @@ import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.common.EntersBattlefieldAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.SourceHasCountersCondition;
import mage.abilities.decorator.ConditionalTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
@ -13,7 +13,6 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
@ -40,7 +39,7 @@ public final class IronApprentice extends CardImpl {
// When Iron Apprentice dies, if it had counters on it, put those counters on target creature you control.
Ability ability = new ConditionalTriggeredAbility(
new DiesSourceTriggeredAbility(new IronApprenticeEffect()), IronApprenticeCondition.instance,
new DiesSourceTriggeredAbility(new IronApprenticeEffect()), SourceHasCountersCondition.instance,
"When {this} dies, if it had counters on it, put those counters on target creature you control."
);
ability.addTarget(new TargetControlledCreaturePermanent());
@ -57,21 +56,6 @@ public final class IronApprentice extends CardImpl {
}
}
enum IronApprenticeCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = source.getSourcePermanentOrLKI(game);
return permanent != null && permanent
.getCounters(game)
.values()
.stream()
.mapToInt(Counter::getCount)
.anyMatch(x -> x > 0);
}
}
class IronApprenticeEffect extends OneShotEffect {
IronApprenticeEffect() {
@ -98,7 +82,6 @@ class IronApprenticeEffect extends OneShotEffect {
.getCounters(game)
.copy()
.values()
.stream()
.forEach(counter -> creature.addCounters(counter, source, game));
return true;
}

View file

@ -24,11 +24,7 @@ import mage.players.Player;
import mage.target.common.TargetCardInHand;
import java.util.UUID;
import mage.abilities.costs.Costs;
import mage.cards.AdventureCard;
import mage.cards.ModalDoubleFacesCardHalf;
import mage.cards.SplitCard;
import mage.cards.SplitCardHalf;
import mage.util.CardUtil;
/**
* @author TheElk801
@ -120,96 +116,10 @@ class JadziOracleOfArcaviosEffect extends OneShotEffect {
}
// query player
if (!controller.chooseUse(outcome, "Cast " + card.getName() + " by paying {1}?", source, game)) {
if (!controller.chooseUse(outcome, "Cast revealed card " + card.getName() + " by paying {1}?", source, game)) {
return false;
}
// handle split-cards
if (card instanceof SplitCard) {
SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard();
SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard();
// get additional cost if any
Costs additionalCostsLeft = leftHalfCard.getSpellAbility().getCosts();
Costs additionalCostsRight = rightHalfCard.getSpellAbility().getCosts();
// set alternative cost and any additional cost
controller.setCastSourceIdWithAlternateMana(leftHalfCard.getId(), new ManaCostsImpl<>("{1}"), additionalCostsLeft);
controller.setCastSourceIdWithAlternateMana(rightHalfCard.getId(), new ManaCostsImpl<>("{1}"), additionalCostsRight);
// allow the card to be cast
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), Boolean.TRUE);
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), Boolean.TRUE);
}
// handle MDFC
if (card instanceof ModalDoubleFacesCard) {
ModalDoubleFacesCardHalf leftHalfCard = ((ModalDoubleFacesCard) card).getLeftHalfCard();
ModalDoubleFacesCardHalf rightHalfCard = ((ModalDoubleFacesCard) card).getRightHalfCard();
// some MDFC cards are lands. IE: sea gate restoration
if (!leftHalfCard.isLand(game)) {
// get additional cost if any
Costs additionalCostsMDFCLeft = leftHalfCard.getSpellAbility().getCosts();
// set alternative cost and any additional cost
controller.setCastSourceIdWithAlternateMana(leftHalfCard.getId(), new ManaCostsImpl<>("{1}"), additionalCostsMDFCLeft);
}
if (!rightHalfCard.isLand(game)) {
// get additional cost if any
Costs additionalCostsMDFCRight = rightHalfCard.getSpellAbility().getCosts();
// set alternative cost and any additional cost
controller.setCastSourceIdWithAlternateMana(rightHalfCard.getId(), new ManaCostsImpl<>("{1}"), additionalCostsMDFCRight);
}
// allow the card to be cast
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), Boolean.TRUE);
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), Boolean.TRUE);
}
// handle adventure cards
if (card instanceof AdventureCard) {
Card creatureCard = card.getMainCard();
Card spellCard = ((AdventureCard) card).getSpellCard();
// get additional cost if any
Costs additionalCostsCreature = creatureCard.getSpellAbility().getCosts();
Costs additionalCostsSpellCard = spellCard.getSpellAbility().getCosts();
// set alternative cost and any additional cost
controller.setCastSourceIdWithAlternateMana(creatureCard.getId(), new ManaCostsImpl<>("{1}"), additionalCostsCreature);
controller.setCastSourceIdWithAlternateMana(spellCard.getId(), new ManaCostsImpl<>("{1}"), additionalCostsSpellCard);
// allow the card to be cast
game.getState().setValue("PlayFromNotOwnHandZone" + creatureCard.getId(), Boolean.TRUE);
game.getState().setValue("PlayFromNotOwnHandZone" + spellCard.getId(), Boolean.TRUE);
}
// normal card
if (!(card instanceof SplitCard)
|| !(card instanceof ModalDoubleFacesCard)
|| !(card instanceof AdventureCard)) {
// get additional cost if any
Costs additionalCostsNormalCard = card.getSpellAbility().getCosts();
controller.setCastSourceIdWithAlternateMana(card.getMainCard().getId(), new ManaCostsImpl<>("{1}"), additionalCostsNormalCard);
}
// cast it
controller.cast(controller.chooseAbilityForCast(card.getMainCard(), game, false),
game, false, new ApprovingObject(source, game));
// turn off effect after cast on every possible card-face
if (card instanceof SplitCard) {
SplitCardHalf leftHalfCard = ((SplitCard) card).getLeftHalfCard();
SplitCardHalf rightHalfCard = ((SplitCard) card).getRightHalfCard();
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null);
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null);
}
if (card instanceof ModalDoubleFacesCard) {
ModalDoubleFacesCardHalf leftHalfCard = ((ModalDoubleFacesCard) card).getLeftHalfCard();
ModalDoubleFacesCardHalf rightHalfCard = ((ModalDoubleFacesCard) card).getRightHalfCard();
game.getState().setValue("PlayFromNotOwnHandZone" + leftHalfCard.getId(), null);
game.getState().setValue("PlayFromNotOwnHandZone" + rightHalfCard.getId(), null);
}
if (card instanceof AdventureCard) {
Card creatureCard = card.getMainCard();
Card spellCard = ((AdventureCard) card).getSpellCard();
game.getState().setValue("PlayFromNotOwnHandZone" + creatureCard.getId(), null);
game.getState().setValue("PlayFromNotOwnHandZone" + spellCard.getId(), null);
}
// turn off effect on a normal card
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
CardUtil.castSingle(controller, source, game, card, new ManaCostsImpl<>("{1}"));
return true;
}

View file

@ -18,7 +18,7 @@ public final class JoinTheMaestros extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}");
// Casualty 2
this.addAbility(new CasualtyAbility(this, 2));
this.addAbility(new CasualtyAbility(2));
// Create a 4/3 black Ogre Warrior creature token.
this.getSpellAbility().addEffect(new CreateTokenEffect(new OgreWarriorToken()));

View file

@ -4,8 +4,8 @@ package mage.cards.l;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.common.TargetOfOpponentsSpellOrAbilityTriggeredAbility;
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.cards.CardImpl;
@ -13,7 +13,6 @@ import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.watchers.common.CardsAmountDrawnThisTurnWatcher;
@ -35,7 +34,7 @@ public final class LeovoldEmissaryOfTrest extends CardImpl {
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new LeovoldEmissaryOfTrestEffect()), new CardsAmountDrawnThisTurnWatcher());
// Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls, you may draw a card.
this.addAbility(new LeovoldEmissaryOfTrestTriggeredAbility());
this.addAbility(new TargetOfOpponentsSpellOrAbilityTriggeredAbility(new DrawCardSourceControllerEffect(1), true));
}
private LeovoldEmissaryOfTrest(final LeovoldEmissaryOfTrest card) {
@ -81,44 +80,4 @@ class LeovoldEmissaryOfTrestEffect extends ContinuousRuleModifyingEffectImpl {
return watcher != null && controller != null && watcher.getAmountCardsDrawn(event.getPlayerId()) >= 1
&& game.isOpponent(controller, event.getPlayerId());
}
}
class LeovoldEmissaryOfTrestTriggeredAbility extends TriggeredAbilityImpl {
LeovoldEmissaryOfTrestTriggeredAbility() {
super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1), true);
setTriggerPhrase("Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls, ");
}
LeovoldEmissaryOfTrestTriggeredAbility(final LeovoldEmissaryOfTrestTriggeredAbility ability) {
super(ability);
}
@Override
public LeovoldEmissaryOfTrestTriggeredAbility copy() {
return new LeovoldEmissaryOfTrestTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.TARGETED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Player controller = game.getPlayer(this.getControllerId());
Player targetter = game.getPlayer(event.getPlayerId());
if (controller != null && targetter != null
&& game.isOpponent(controller, targetter.getId())) {
if (event.getTargetId().equals(controller.getId())) {
return true; // Player was targeted
}
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId());
if (permanent != null && this.isControlledBy(permanent.getControllerId())) {
return true;
}
}
return false;
}
}

View file

@ -18,7 +18,7 @@ public final class LightEmUp extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}");
// Casualty 2
this.addAbility(new CasualtyAbility(this, 2));
this.addAbility(new CasualtyAbility(2));
// Light 'Em Up deals 2 damage to target creature or planeswalker.
this.getSpellAbility().addEffect(new DamageTargetEffect(2));

View file

@ -19,7 +19,7 @@ public final class MakeDisappear extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}");
// Casualty 1
this.addAbility(new CasualtyAbility(this, 1));
this.addAbility(new CasualtyAbility(1));
// Counter target spell unless its controller pays {2}.
this.getSpellAbility().addEffect(new CounterUnlessPaysEffect(new GenericManaCost(2)));

View file

@ -0,0 +1,203 @@
package mage.cards.m;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.common.RemoveCounterCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.keyword.DeathtouchAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.FilterCard;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.card.CastFromZonePredicate;
import mage.filter.predicate.card.FaceDownPredicate;
import mage.filter.predicate.card.OwnerIdPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.game.permanent.PermanentToken;
import mage.game.permanent.token.TreasureToken;
import mage.players.Player;
import mage.target.TargetCard;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class MariTheKillingQuill extends CardImpl {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Assassins, Mercenaries, and Rogues");
static {
filter.add(Predicates.or(
SubType.ASSASSIN.getPredicate(),
SubType.MERCENARY.getPredicate(),
SubType.ROGUE.getPredicate()
));
}
public MariTheKillingQuill(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{B}");
this.addSuperType(SuperType.LEGENDARY);
this.addSubType(SubType.VAMPIRE, SubType.ASSASSIN);
this.power = new MageInt(3);
this.toughness = new MageInt(2);
// Whenever a creature an opponent controls dies, exile it with a hit counter on it.
this.addAbility(new SimpleStaticAbility(new MariTheKillingQuillReplacementEffect()));
// Assassins, Mercenaries, and Rogues you control have deathtouch and
// "Whenever this creature deals combat damage to a player, you may remove a hit counter from a card that player owns in exile.
// If you do, draw a card and create two Treasure tokens."
GainAbilityControlledEffect gainDeathTouchEffect = new GainAbilityControlledEffect(DeathtouchAbility.getInstance(), Duration.WhileOnBattlefield, filter);
Ability mainAbility = new SimpleStaticAbility(Zone.BATTLEFIELD, gainDeathTouchEffect);
// NOTE: Optional part is handled inside the effect
Ability dealsDamageAbility = new DealsCombatDamageToAPlayerTriggeredAbility(new MariTheKillingQuillDealsDamageEffect(), false, true);
Effect drawAndTreasureEffect = new GainAbilityControlledEffect(dealsDamageAbility, Duration.WhileOnBattlefield, filter);
drawAndTreasureEffect.setText(
"\"Whenever this creature deals combat damage to a player, you may remove a hit counter from a card taht player owns in exile. " +
"If you do, draw a card and create two Treasure tokens.\"");
drawAndTreasureEffect.concatBy("and");
mainAbility.addEffect(drawAndTreasureEffect);
this.addAbility(mainAbility);
}
private MariTheKillingQuill(final MariTheKillingQuill card) {
super(card);
}
@Override
public MariTheKillingQuill copy() {
return new MariTheKillingQuill(this);
}
}
class MariTheKillingQuillDealsDamageEffect extends OneShotEffect {
MariTheKillingQuillDealsDamageEffect() {
super(Outcome.Benefit);
}
private MariTheKillingQuillDealsDamageEffect(final MariTheKillingQuillDealsDamageEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source));
if (controller == null || opponent == null) {
return false;
}
FilterCard filterCard = new FilterCard("a card that player owns in exile");
filterCard.add(new OwnerIdPredicate(opponent.getId()));
filterCard.add(Predicates.not(FaceDownPredicate.instance));
filterCard.add(new CastFromZonePredicate(Zone.EXILED));
Effect doIfCostPaidEffect = new DoIfCostPaid(
new MariTheKillingQuillDrawAndTokenEffect(),
new RemoveCounterCost(new TargetCard(Zone.EXILED, filterCard))
);
return doIfCostPaidEffect.apply(game, source);
}
@Override
public MariTheKillingQuillDealsDamageEffect copy() {
return new MariTheKillingQuillDealsDamageEffect(this);
}
}
class MariTheKillingQuillDrawAndTokenEffect extends OneShotEffect {
MariTheKillingQuillDrawAndTokenEffect() {
super(Outcome.Benefit);
}
private MariTheKillingQuillDrawAndTokenEffect(final MariTheKillingQuillDrawAndTokenEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Effect drawCardEffect = new DrawCardSourceControllerEffect(1);
Effect createTreasureEffect = new CreateTokenEffect(new TreasureToken(), 2);
boolean success = drawCardEffect.apply(game, source);
success |= createTreasureEffect.apply(game, source);
return success;
}
@Override
public MariTheKillingQuillDrawAndTokenEffect copy() {
return new MariTheKillingQuillDrawAndTokenEffect(this);
}
}
// Based on Draugr Necromancer
class MariTheKillingQuillReplacementEffect extends ReplacementEffectImpl {
MariTheKillingQuillReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Exile);
staticText = "Whenever a creature an opponent controls dies, exile it with a hit counter on it.";
}
private MariTheKillingQuillReplacementEffect(final MariTheKillingQuillReplacementEffect effect) {
super(effect);
}
@Override
public MariTheKillingQuillReplacementEffect copy() {
return new MariTheKillingQuillReplacementEffect(this);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
Permanent permanent = ((ZoneChangeEvent) event).getTarget();
Player controller = game.getPlayer(source.getControllerId());
if (controller == null
|| permanent == null
|| !controller.hasOpponent(permanent.getControllerId(), game)) {
return false;
}
return CardUtil.moveCardWithCounter(game, source, controller, permanent, Zone.EXILED, CounterType.HIT.createInstance());
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
ZoneChangeEvent zce = (ZoneChangeEvent) event;
return zce.isDiesEvent()
&& zce.getTarget().isCreature(game)
&& !(zce.getTarget() instanceof PermanentToken);
}
}

View file

@ -28,8 +28,8 @@ import java.util.*;
* @author Alex-Vasile
*/
public class MasterOfCeremonies extends CardImpl {
public MasterOfCeremonies(UUID ownderId, CardSetInfo setInfo) {
super(ownderId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
public MasterOfCeremonies(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}");
this.subtype.add(SubType.RHINO);
this.subtype.add(SubType.DRUID);

View file

@ -23,8 +23,8 @@ public class MastersRebuke extends CardImpl {
filter.add(TargetController.NOT_YOU.getControllerPredicate());
}
public MastersRebuke(UUID ownderId, CardSetInfo setInfo) {
super(ownderId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}");
public MastersRebuke(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{G}");
// Target creature you control deals damage equal to its power to target creature or planeswalker you dont control.
this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect());

View file

@ -29,8 +29,8 @@ public class MiragePhalanx extends CardImpl {
"except it has haste and loses soulbond. " +
"Exile it at end of combat.\"";
public MiragePhalanx(UUID ownderId, CardSetInfo setInfo) {
super(ownderId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}");
public MiragePhalanx(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}{R}");
this.addSubType(SubType.HUMAN);
this.addSubType(SubType.SOLDIER);

View file

@ -34,8 +34,8 @@ public class MyojinOfCrypticDreams extends CardImpl {
permanentSpellFilter.add(PermanentPredicate.instance);
}
public MyojinOfCrypticDreams(UUID ownderId, CardSetInfo setInfo) {
super(ownderId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}{U}");
public MyojinOfCrypticDreams(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{U}{U}{U}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.SPIRIT);

View file

@ -37,8 +37,8 @@ public class MyojinOfGrimBetrayal extends CardImpl {
private static final DynamicValue xValue = new CardsInAllGraveyardsCount(filter);
private static final Hint hint = new ValueHint("Permanents put into the graveyard this turn", xValue);
public MyojinOfGrimBetrayal(UUID ownderId, CardSetInfo setInfo) {
super(ownderId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}{B}{B}");
public MyojinOfGrimBetrayal(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{B}{B}{B}");
this.addSuperType(SuperType.LEGENDARY);
this.subtype.add(SubType.SPIRIT);

View file

@ -8,14 +8,13 @@ import mage.abilities.common.CanBeYourCommanderAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.cards.Card;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.SuperType;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.common.FilterControlledPermanent;
import mage.game.Game;
@ -25,8 +24,6 @@ import mage.game.permanent.token.NahiriTheLithomancerEquipmentToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInHand;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.common.TargetControlledPermanent;
/**
@ -35,6 +32,12 @@ import mage.target.common.TargetControlledPermanent;
*/
public final class NahiriTheLithomancer extends CardImpl {
private static final FilterCard filter = new FilterCard("an Equipment");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
}
public NahiriTheLithomancer(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{3}{W}{W}");
this.addSuperType(SuperType.LEGENDARY);
@ -46,7 +49,7 @@ public final class NahiriTheLithomancer extends CardImpl {
this.addAbility(new LoyaltyAbility(new NahiriTheLithomancerFirstAbilityEffect(), 2));
// -2: You may put an Equipment card from your hand or graveyard onto the battlefield.
this.addAbility(new LoyaltyAbility(new NahiriTheLithomancerSecondAbilityEffect(), -2));
this.addAbility(new LoyaltyAbility(new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(filter), -2));
// -10: Create a colorless Equipment artifact token named Stoneforged Blade. It has indestructible, "Equipped creature gets +5/+5 and has double strike," and equip {0}.
Effect effect = new CreateTokenEffect(new NahiriTheLithomancerEquipmentToken());
@ -122,50 +125,3 @@ class NahiriTheLithomancerFirstAbilityEffect extends OneShotEffect {
return false;
}
}
class NahiriTheLithomancerSecondAbilityEffect extends OneShotEffect {
private static final FilterCard filter = new FilterCard("an Equipment");
static {
filter.add(SubType.EQUIPMENT.getPredicate());
}
NahiriTheLithomancerSecondAbilityEffect() {
super(Outcome.PutCardInPlay);
this.staticText = "You may put an Equipment card from your hand or graveyard onto the battlefield";
}
NahiriTheLithomancerSecondAbilityEffect(final NahiriTheLithomancerSecondAbilityEffect effect) {
super(effect);
}
@Override
public NahiriTheLithomancerSecondAbilityEffect copy() {
return new NahiriTheLithomancerSecondAbilityEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
if (controller.chooseUse(Outcome.PutCardInPlay, "Put an Equipment from hand? (No = from graveyard)", source, game)) {
Target target = new TargetCardInHand(0, 1, filter);
controller.choose(outcome, target, source, game);
Card card = controller.getHand().get(target.getFirstTarget(), game);
if (card != null) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game);
}
} else {
Target target = new TargetCardInYourGraveyard(0, 1, filter);
target.choose(Outcome.PutCardInPlay, source.getControllerId(), source.getSourceId(), source, game);
Card card = controller.getGraveyard().get(target.getFirstTarget(), game);
if (card != null) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game);
}
}
return true;
}
return false;
}
}

View file

@ -0,0 +1,106 @@
package mage.cards.n;
import mage.Mana;
import mage.abilities.Ability;
import mage.abilities.common.DiesAttachedTriggeredAbility;
import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.ReturnToBattlefieldAttachedEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.card.CastFromZonePredicate;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.TargetPermanent;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author Alex-Vasile
*/
public class NextOfKin extends CardImpl {
public NextOfKin(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}");
this.addSubType(SubType.AURA);
// Enchant creature
TargetPermanent auraTarget = new TargetCreaturePermanent();
this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.Benefit));
this.addAbility(new EnchantAbility(auraTarget.getTargetName()));
// When enchanted creature dies, you may put a creature card you own with lesser mana value from your hand or from the command zone onto the battlefield.
// If you do, return Next of Kin to the battlefield attached to that creature at the beginning of the next end step.
this.addAbility(new DiesAttachedTriggeredAbility(new NextOfKinDiesEffect(), "enchanted creature", true));
}
private NextOfKin(final NextOfKin card) {
super(card);
}
@Override
public NextOfKin copy() {
return new NextOfKin(this);
}
}
class NextOfKinDiesEffect extends OneShotEffect {
NextOfKinDiesEffect() {
super(Outcome.Benefit);
this.staticText = "you may put a creature card you own with lesser mana value from your hand or from the command zone onto the battlefield." +
"If you do, return Next of Kin to the battlefield attached to that creature at the beginning of the next end step.";
}
private NextOfKinDiesEffect(final NextOfKinDiesEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Card nextOfKinCard = (Card) source.getSourceObjectIfItStillExists(game);
Object object = getValue("attachedTo");
if (controller == null || nextOfKinCard == null || !(object instanceof Permanent)) {
return false;
}
int manaValue = ((Permanent) object).getManaValue();
FilterCreatureCard filterCreatureCard = new FilterCreatureCard("a creature card you own with lesser mana value from your hand or from the command zone");
filterCreatureCard.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, manaValue));
// This effect is used only to get the info about which card was added.
Effect hackTargetEffect = new InfoEffect("");
Effect putCardEffect = new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(filterCreatureCard, false, hackTargetEffect, Zone.HAND, Zone.COMMAND);
boolean cardPut = putCardEffect.apply(game, source);
if (!cardPut) {
return false;
}
Effect returnToBattlefieldAttachedEffect = new ReturnToBattlefieldAttachedEffect();
returnToBattlefieldAttachedEffect.setTargetPointer(hackTargetEffect.getTargetPointer());
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(returnToBattlefieldAttachedEffect), source);
return true;
}
@Override
public NextOfKinDiesEffect copy() {
return new NextOfKinDiesEffect(this);
}
}

View file

@ -3,29 +3,26 @@ package mage.cards.n;
import mage.abilities.Ability;
import mage.abilities.LoyaltyAbility;
import mage.abilities.common.LandfallAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.UntapTargetEffect;
import mage.abilities.effects.common.continuous.BecomesCreatureTargetEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.HasteAbility;
import mage.abilities.keyword.MenaceAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.filter.predicate.card.CardManaCostLessThanControlledLandCountPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.permanent.token.custom.CreatureToken;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.TargetPermanent;
import mage.target.common.TargetCardInHand;
import mage.target.common.TargetCardInYourGraveyard;
import java.util.UUID;
@ -36,6 +33,11 @@ import static mage.constants.Outcome.Benefit;
*/
public final class NissaOfShadowedBoughs extends CardImpl {
private static final FilterCard filter = new FilterCard("card with mana value less than or equal to the number of lands you control");
static {
filter.add(CardManaCostLessThanControlledLandCountPredicate.getInstance());
}
public NissaOfShadowedBoughs(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.PLANESWALKER}, "{2}{B}{G}");
@ -52,8 +54,13 @@ public final class NissaOfShadowedBoughs extends CardImpl {
ability.addTarget(new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND));
this.addAbility(ability);
// 5: You may put a creature card with converted mana cost less than or equal to the number of lands you control onto the battlefield from your hand or graveyard with two +1/+1 counters on it.
this.addAbility(new LoyaltyAbility(new NissaOfShadowedBoughsCreatureEffect(), -5));
// 5: You may put a creature card with mana value less than or equal to the number of lands you control onto the battlefield from your hand or graveyard with two +1/+1 counters on it.
Effect putCardEffect = new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(filter, false, new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)));
putCardEffect.setText("You may put a creature card with mana value less than or equal to " +
"the number of lands you control onto the battlefield from your hand or graveyard " +
"with two +1/+1 counters on it.");
this.addAbility(new LoyaltyAbility(putCardEffect,-5)
);
}
private NissaOfShadowedBoughs(final NissaOfShadowedBoughs card) {
@ -99,61 +106,3 @@ class NissaOfShadowedBoughsLandEffect extends OneShotEffect {
return true;
}
}
class NissaOfShadowedBoughsCreatureEffect extends OneShotEffect {
NissaOfShadowedBoughsCreatureEffect() {
super(Outcome.Benefit);
staticText = "You may put a creature card with mana value less than or equal to " +
"the number of lands you control onto the battlefield from your hand or graveyard " +
"with two +1/+1 counters on it.";
}
private NissaOfShadowedBoughsCreatureEffect(final NissaOfShadowedBoughsCreatureEffect effect) {
super(effect);
}
@Override
public NissaOfShadowedBoughsCreatureEffect copy() {
return new NissaOfShadowedBoughsCreatureEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
if (player == null) {
return false;
}
int lands = game.getBattlefield().count(
StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND,
source.getControllerId(), source, game
);
FilterCard filter = new FilterCreatureCard("creature card with mana value " + lands + " or less");
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, lands + 1));
int inHand = player.getHand().count(filter, game);
int inGrave = player.getGraveyard().count(filter, game);
if (inHand < 1 && inGrave < 1) {
return false;
}
TargetCard target;
if (inHand < 1 || (inGrave > 0 && !player.chooseUse(
outcome, "Put a creature card from your hand or graveyard onto the battlefield?",
null, "Hand", "Graveyard", source, game
))) {
target = new TargetCardInYourGraveyard(0, 1, filter, true);
} else {
target = new TargetCardInHand(filter);
}
player.choose(outcome, target, source, game);
Card card = game.getCard(target.getFirstTarget());
if (card == null) {
return false;
}
player.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent permanent = game.getPermanent(card.getId());
if (permanent != null) {
permanent.addCounters(CounterType.P1P1.createInstance(2), source.getControllerId(), source, game);
}
return true;
}
}

View file

@ -69,6 +69,7 @@ public final class ObNixilisTheAdversary extends CardImpl {
}
}
// TODO: Would be nice to refactor into Casualty ability so this doesn't have a custom implementation to maintain
class ObNixilisTheAdversaryCasualtyAbility extends StaticAbility {
public ObNixilisTheAdversaryCasualtyAbility(Card card) {

View file

@ -0,0 +1,120 @@
package mage.cards.o;
import mage.MageInt;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DiscardCardControllerTriggeredAbility;
import mage.abilities.effects.common.cost.SpellCostReductionForEachSourceEffect;
import mage.abilities.hint.ValueHint;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class OskarRubbishReclaimer extends CardImpl {
private static final ValueHint hint = new ValueHint("Number of different mana values in your graveyard", OskarRubbishReclaimerValue.instance);
public OskarRubbishReclaimer(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{B}");
this.addSuperType(SuperType.LEGENDARY);
this.addSubType(SubType.HUMAN, SubType.WIZARD);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// This spell costs {1} less to cast for each different mana value among cards in your graveyard.
Effect spellReductionEffect = new SpellCostReductionForEachSourceEffect(1, OskarRubbishReclaimerValue.instance);
this.addAbility(new SimpleStaticAbility(Zone.ALL, spellReductionEffect).addHint(hint));
// Whenever you discard a nonland card, you may cast it from your graveyard.
// Optinal part is handled by the effect
this.addAbility(new DiscardCardControllerTriggeredAbility(new OskarRubbishReclaimerCastEffect(), false, StaticFilters.FILTER_CARD_A_NON_LAND));
}
private OskarRubbishReclaimer(final OskarRubbishReclaimer card) {
super(card);
}
@Override
public OskarRubbishReclaimer copy() {
return new OskarRubbishReclaimer(this);
}
}
class OskarRubbishReclaimerCastEffect extends OneShotEffect {
OskarRubbishReclaimerCastEffect() {
super(Outcome.Benefit);
this.staticText = "you may cast it from your graveyard.";
}
private OskarRubbishReclaimerCastEffect(final OskarRubbishReclaimerCastEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Card card = (Card) getValue("discardedCard");
if (controller == null || card == null || game.getState().getZone(card.getId()) != Zone.GRAVEYARD) {
return false;
}
if (!controller.chooseUse(Outcome.Benefit, "Cast " + card.getName() + "?", source, game)) {
return false;
}
CardUtil.castSingle(controller, source, game, card);
return true;
}
@Override
public OskarRubbishReclaimerCastEffect copy() {
return new OskarRubbishReclaimerCastEffect(this);
}
}
enum OskarRubbishReclaimerValue implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
Player player = game.getPlayer(sourceAbility.getControllerId());
return player == null ? 0 : player
.getGraveyard()
.getCards(game)
.stream()
.map(MageObject::getManaValue)
.distinct()
.mapToInt(x -> 1)
.sum();
}
@Override
public OskarRubbishReclaimerValue copy() {
return this;
}
@Override
public String getMessage() {
return "different mana value among cards in your graveyard";
}
@Override
public String toString() {
return "1";
}
}

View file

@ -0,0 +1,118 @@
package mage.cards.p;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.TargetOfOpponentsSpellOrAbilityTriggeredAbility;
import mage.abilities.costs.common.PayLifeCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CounterUnlessPaysEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.common.TargetOpponent;
import mage.target.targetpointer.FixedTarget;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class ParnesseTheSubtleBrush extends CardImpl {
protected static final String SPELL_KEY = "castCopiedSpell";
public ParnesseTheSubtleBrush(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{B}{R}");
this.addSuperType(SuperType.LEGENDARY);
this.addSubType(SubType.VAMPIRE, SubType.WIZARD);
this.power = new MageInt(4);
this.toughness = new MageInt(4);
// Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls,
// counter that spell or ability unless that player pays 4 life.
this.addAbility(new TargetOfOpponentsSpellOrAbilityTriggeredAbility(new CounterUnlessPaysEffect(new PayLifeCost(4).setText("4 life"))));
// Whenever you copy a spell, up to one target opponent may also copy that spell.
// They may choose new targets for that copy.
this.addAbility(new ParnesseTheSubtleBrushCopySpellTriggeredAbility());
}
private ParnesseTheSubtleBrush(final ParnesseTheSubtleBrush card) {
super(card);
}
@Override
public ParnesseTheSubtleBrush copy() {
return new ParnesseTheSubtleBrush(this);
}
}
class ParnesseTheSubtleBrushCopySpellOpponentEffect extends OneShotEffect {
ParnesseTheSubtleBrushCopySpellOpponentEffect() {
super(Outcome.Detriment);
this.staticText = "up to one target opponent may also copy that spell. " +
"They may choose new targets for that copy";
}
ParnesseTheSubtleBrushCopySpellOpponentEffect(final ParnesseTheSubtleBrushCopySpellOpponentEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player opponent = game.getPlayer(source.getFirstTarget());
Object object = getValue(ParnesseTheSubtleBrush.SPELL_KEY);
if (opponent == null || !(object instanceof Spell)) {
return false;
}
Spell spellToCopy = (Spell) object;
spellToCopy.createCopyOnStack(game, source, opponent.getId(), true);
return true;
}
@Override
public Effect copy() {
return new ParnesseTheSubtleBrushCopySpellOpponentEffect(this);
}
}
class ParnesseTheSubtleBrushCopySpellTriggeredAbility extends TriggeredAbilityImpl {
ParnesseTheSubtleBrushCopySpellTriggeredAbility() {
super(Zone.BATTLEFIELD, new ParnesseTheSubtleBrushCopySpellOpponentEffect(), true);
this.getTargets().add(new TargetOpponent(0, 1, false));
}
private ParnesseTheSubtleBrushCopySpellTriggeredAbility(final ParnesseTheSubtleBrushCopySpellTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.COPIED_STACKOBJECT;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Spell spell = game.getSpell(event.getTargetId());
if (spell == null || !spell.isControlledBy(this.getControllerId())) {
return false;
}
getEffects().setValue(ParnesseTheSubtleBrush.SPELL_KEY, spell);
return true;
}
@Override
public TriggeredAbility copy() {
return new ParnesseTheSubtleBrushCopySpellTriggeredAbility(this);
}
}

View file

@ -0,0 +1,124 @@
package mage.cards.p;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfCombatTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.dynamicvalue.common.ParleyCount;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DrawCardAllEffect;
import mage.abilities.effects.common.continuous.BoostControlledEffect;
import mage.abilities.effects.common.continuous.GainAbilityControlledEffect;
import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.predicate.permanent.TokenPredicate;
import mage.game.Game;
import mage.game.permanent.token.CitizenGreenWhiteToken;
import mage.game.permanent.token.Token;
import mage.players.Player;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class PhabineBosssConfidant extends CardImpl {
private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent("Creature tokens you control");
static {
filter.add(TokenPredicate.TRUE);
}
public PhabineBosssConfidant(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{G}{W}");
addSuperType(SuperType.LEGENDARY);
addSubType(SubType.CAT, SubType.ADVISOR);
this.power = new MageInt(3);
this.toughness = new MageInt(6);
// Creature tokens you control have haste.
this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect(
HasteAbility.getInstance(),
Duration.WhileOnBattlefield,
filter)
));
// Parley At the beginning of combat on your turn, each player reveals the top card of their library.
// For each land card revealed this way, you create a 1/1 green and white Citizen creature token.
// Then creatures you control get +1/+1 until end of turn for each nonland card revealed this way.
// Then each player draws a card.
Ability parleyAbility = new BeginningOfCombatTriggeredAbility(
new PhabineBosssConfidantParleyEffect(),
TargetController.YOU,
false
);
Effect drawCardAllEffect = new DrawCardAllEffect(1);
drawCardAllEffect.concatBy("Then");
parleyAbility.addEffect(drawCardAllEffect);
parleyAbility.setAbilityWord(AbilityWord.PARLEY);
this.addAbility(parleyAbility);
}
private PhabineBosssConfidant(final PhabineBosssConfidant card) {
super(card);
}
@Override
public PhabineBosssConfidant copy() {
return new PhabineBosssConfidant(this);
}
}
class PhabineBosssConfidantParleyEffect extends OneShotEffect {
PhabineBosssConfidantParleyEffect() {
super(Outcome.Benefit);
this.staticText = "each player reveals the top card of their library. " +
"For each land card revealed this way, you create a 1/1 green and white Citizen creature token. " +
"Then creatures you control get +1/+1 until end of turn for each nonland card revealed this way.";
}
private PhabineBosssConfidantParleyEffect(final PhabineBosssConfidantParleyEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
int landCount = ParleyCount.getInstance().calculate(game, source, this);
int nonEmptyLibraries = 0;
for (UUID playerID : game.getState().getPlayersInRange(controller.getId(), game)) {
Player player = game.getPlayer(playerID);
if (player != null && player.getLibrary().size() != 0) {
nonEmptyLibraries++;
}
}
int nonLandCount = nonEmptyLibraries - landCount;
if (landCount > 0) {
Token citizenToken = new CitizenGreenWhiteToken();
citizenToken.putOntoBattlefield(landCount, game, source, source.getControllerId(), false, false);
}
if (nonLandCount > 0) {
Effect boostEffect = new BoostControlledEffect(nonLandCount, nonLandCount, Duration.EndOfTurn);
boostEffect.apply(game, source);
}
return true;
}
@Override
public PhabineBosssConfidantParleyEffect copy() {
return new PhabineBosssConfidantParleyEffect(this);
}
}

View file

@ -0,0 +1,160 @@
package mage.cards.r;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.Condition;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.common.CreateTokenEffect;
import mage.abilities.keyword.CascadeAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.TreasureToken;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import mage.players.Player;
import mage.watchers.Watcher;
import mage.watchers.common.ManaPaidSourceWatcher;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class RainOfRiches extends CardImpl {
public RainOfRiches(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{3}{R}{R}");
// When Rain of Riches enters the battlefield, create two Treasure tokens.
this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenEffect(new TreasureToken(), 2)));
// The first spell you cast each turn that mana from a Treasure was spent to cast has cascade.
// (When you cast the spell, exile cards from the top of your library until you exile a nonland card that costs less.
// You may cast it without paying its mana cost.
// Put the exiled cards on the bottom of your library in a random order.)
this.addAbility(
new SimpleStaticAbility(Zone.BATTLEFIELD, new RainOfRichesGainsCascadeEffect()),
new RainOfRichesWatcher()
);
}
private RainOfRiches(final RainOfRiches card) {
super(card);
}
@Override
public RainOfRiches copy() {
return new RainOfRiches(this);
}
}
class RainOfRichesGainsCascadeEffect extends ContinuousEffectImpl {
private final Ability cascadeAbility = new CascadeAbility();
RainOfRichesGainsCascadeEffect() {
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
this.staticText =
"The first spell you cast each turn that mana from a Treasure was spent to cast has cascade. " +
"<i>(When you cast the spell, exile cards from the top of your library until you exile a nonland card that costs less. " +
"You may cast it without paying its mana cost. " +
"Put the exiled cards on the bottom of your library in a random order.)</i>";
}
private RainOfRichesGainsCascadeEffect(final RainOfRichesGainsCascadeEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
RainOfRichesWatcher watcher = game.getState().getWatcher(RainOfRichesWatcher.class);
if (controller == null || watcher == null) {
return false;
}
for (StackObject stackObject : game.getStack()) {
// Only spells cast, so no copies of spells
if ((stackObject instanceof Spell)
&& !stackObject.isCopy()
&& stackObject.isControlledBy(source.getControllerId())) {
Spell spell = (Spell) stackObject;
if (FirstSpellCastWithTreasureCondition.instance.apply(game, source)) {
game.getState().addOtherAbility(spell.getCard(), cascadeAbility);
return true; // TODO: I think this should return here as soon as it finds the first one.
// If it should, change WildMageSorcerer to also return early.
}
}
}
return false;
}
@Override
public RainOfRichesGainsCascadeEffect copy() {
return new RainOfRichesGainsCascadeEffect(this);
}
}
enum FirstSpellCastWithTreasureCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
if (game.getStack().isEmpty()) {
return false;
}
RainOfRichesWatcher watcher = game.getState().getWatcher(RainOfRichesWatcher.class);
StackObject so = game.getStack().getFirst();
return watcher != null && RainOfRichesWatcher.checkSpell(so, game);
}
}
class RainOfRichesWatcher extends Watcher {
private final Map<UUID, MageObjectReference> playerMap = new HashMap<>();
RainOfRichesWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (event.getType() != GameEvent.EventType.CAST_SPELL) {
return;
}
Spell spell = game.getSpell(event.getSourceId());
if (spell == null) {
return;
}
int manaPaid = ManaPaidSourceWatcher.getTreasurePaid(spell.getId(), game);
if (manaPaid < 1) {
return;
}
playerMap.computeIfAbsent(event.getPlayerId(), x -> new MageObjectReference(spell.getMainCard(), game));
}
@Override
public void reset() {
playerMap.clear();
super.reset();
}
static boolean checkSpell(StackObject stackObject, Game game) {
if (stackObject.isCopy()
|| !(stackObject instanceof Spell)) {
return false;
}
RainOfRichesWatcher watcher = game.getState().getWatcher(RainOfRichesWatcher.class);
return watcher.playerMap.containsKey(stackObject.getControllerId())
&& watcher.playerMap.get(stackObject.getControllerId()).refersTo(((Spell) stackObject).getMainCard(), game);
}
}

View file

@ -3,21 +3,16 @@ package mage.cards.r;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.TargetOfOpponentsSpellOrAbilityTriggeredAbility;
import mage.abilities.condition.common.EnchantedSourceCondition;
import mage.abilities.decorator.ConditionalOneShotEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
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.GameEvent.EventType;
import mage.game.permanent.Permanent;
import mage.players.Player;
/**
*
@ -34,8 +29,15 @@ public final class RayneAcademyChancellor extends CardImpl {
this.power = new MageInt(1);
this.toughness = new MageInt(1);
// Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls, you may draw a card. You may draw an additional card if Rayne, Academy Chancellor is enchanted.
this.addAbility(new RayneAcademyChancellorTriggeredAbility());
// Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls, you may draw a card.
// You may draw an additional card if Rayne, Academy Chancellor is enchanted.
Effect drawEffect = new ConditionalOneShotEffect(
new DrawCardSourceControllerEffect(2),
new DrawCardSourceControllerEffect(1),
new EnchantedSourceCondition(),
"you may draw a card. You may draw an additional card if {this} is enchanted"
);
this.addAbility(new TargetOfOpponentsSpellOrAbilityTriggeredAbility(drawEffect));
}
private RayneAcademyChancellor(final RayneAcademyChancellor card) {
@ -47,45 +49,3 @@ public final class RayneAcademyChancellor extends CardImpl {
return new RayneAcademyChancellor(this);
}
}
class RayneAcademyChancellorTriggeredAbility extends TriggeredAbilityImpl {
RayneAcademyChancellorTriggeredAbility() {
super(Zone.BATTLEFIELD, new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(2), new DrawCardSourceControllerEffect(1), new EnchantedSourceCondition(), "you may draw a card. You may draw an additional card if {this} is enchanted."), true);
}
RayneAcademyChancellorTriggeredAbility(final RayneAcademyChancellorTriggeredAbility ability) {
super(ability);
}
@Override
public RayneAcademyChancellorTriggeredAbility copy() {
return new RayneAcademyChancellorTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.TARGETED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Player controller = game.getPlayer(this.getControllerId());
Player targetter = game.getPlayer(event.getPlayerId());
if (controller != null && targetter != null && !controller.getId().equals(targetter.getId())) {
if (event.getTargetId().equals(controller.getId())) {
return true;
}
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId());
if (permanent != null && this.isControlledBy(permanent.getControllerId())) {
return true;
}
}
return false;
}
@Override
public String getRule() {
return "Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls, you may draw a card. You may draw an additional card if {this} is enchanted.";
}
}

View file

@ -58,13 +58,12 @@ class ReleaseToTheWindEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (targetPermanent != null) {
return PlayFromNotOwnHandZoneTargetEffect.exileAndPlayFromExile(game, source, targetPermanent,
TargetController.OWNER, Duration.Custom, true, false, true);
}
Permanent targetPermanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (controller == null || targetPermanent == null) {
return false;
}
return false;
return PlayFromNotOwnHandZoneTargetEffect.exileAndPlayFromExile(game, source, targetPermanent,
TargetController.OWNER, Duration.Custom, true, false, true);
}
}

View file

@ -0,0 +1,197 @@
package mage.cards.r;
import mage.abilities.Ability;
import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.counters.Counters;
import mage.filter.StaticFilters;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetPermanent;
import mage.target.common.TargetControlledPermanent;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class ResourcefulDefense extends CardImpl {
public static final FilterControlledPermanent filter2 = new FilterControlledPermanent("another permanent");
static {
filter2.add(AnotherPredicate.instance);
}
public ResourcefulDefense(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{W}");
// Whenever a permanent you control leaves the battlefield, if it had counters on it, put those counters on target permanent you control.
Ability ltbAbility = new ResourcefulDefenseTriggeredAbility();
ltbAbility.addTarget(new TargetControlledPermanent(filter2));
this.addAbility(ltbAbility);
// {4}{W}: Move any number of counters from target permanent you control to another target permanent you control.
Ability ability = new SimpleActivatedAbility(new ResourcefulDefenseMoveCounterEffect(), new ManaCostsImpl<>("{4}{W}"));
Target fromTarget = new TargetPermanent(StaticFilters.FILTER_CONTROLLED_PERMANENT);
fromTarget.setTargetTag(1);
Target toTarget = new TargetPermanent(filter2);
toTarget.setTargetTag(2);
ability.addTarget(fromTarget);
ability.addTarget(toTarget);
this.addAbility(ability);
}
private ResourcefulDefense(final ResourcefulDefense card) {
super(card);
}
@Override
public ResourcefulDefense copy() {
return new ResourcefulDefense(this);
}
}
class ResourcefulDefenseMoveCounterEffect extends OneShotEffect {
ResourcefulDefenseMoveCounterEffect() {
super(Outcome.BoostCreature);
this.staticText = "Move any number of counters from target permanent you control to another target permanent you control";
}
private ResourcefulDefenseMoveCounterEffect(final ResourcefulDefenseMoveCounterEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent fromPermanent = game.getPermanent(source.getFirstTarget());
Permanent toPermanent = game.getPermanent(source.getTargets().get(1).getFirstTarget());
if(controller == null || fromPermanent == null || toPermanent == null) {
return false;
}
// Counter name and how many to move
Map<String, Integer> counterMap = new HashMap<>();
for (Map.Entry<String, Counter> entry : fromPermanent.getCounters(game).entrySet()) {
int num = controller.getAmount(
0,
entry.getValue().getCount(),
"Choose how many " + entry.getKey() +
" counters to remove from " + fromPermanent.getLogName(),
game);
int newAmount = num + counterMap.getOrDefault(entry.getKey(), 0);
counterMap.put(entry.getKey(), newAmount);
}
// Move the counters
for (String counterName : counterMap.keySet()) {
toPermanent.addCounters(
CounterType.findByName(counterName).createInstance(counterMap.get(counterName)),
source,
game);
fromPermanent.removeCounters(counterName, counterMap.get(counterName), source, game);
game.informPlayers(
controller.getLogName() + "moved " +
counterMap.get(counterName) + " " +
counterName + "counter" + (counterMap.get(counterName) > 1 ? "s" : "") +
"from " + fromPermanent.getLogName() +
"to " + toPermanent.getLogName() + "."
);
}
return true;
}
@Override
public Effect copy() {
return new ResourcefulDefenseMoveCounterEffect(this);
}
}
class ResourcefulDefenseTriggeredAbility extends LeavesBattlefieldAllTriggeredAbility {
ResourcefulDefenseTriggeredAbility() {
super(new ResourcefulDefenseLeaveEffect(), StaticFilters.FILTER_CONTROLLED_PERMANENT);
setTriggerPhrase("Whenever a creature you control leaves the battlefield, if it had counters on it, ");
}
private ResourcefulDefenseTriggeredAbility(final ResourcefulDefenseTriggeredAbility ability) {
super(ability);
}
public ResourcefulDefenseTriggeredAbility copy() {
return new ResourcefulDefenseTriggeredAbility(this);
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!super.checkTrigger(event, game)) {
return false;
}
Permanent permanent = ((ZoneChangeEvent) event).getTarget();
Player controller = game.getPlayer(permanent.getControllerId());
if (controller == null) {
return false;
}
Counters counters = permanent.getCounters(game);
if (counters.values().stream().mapToInt(Counter::getCount).noneMatch(x -> x > 0)) {
return false;
}
this.getEffects().setValue("counters", counters);
return true;
}
}
class ResourcefulDefenseLeaveEffect extends OneShotEffect {
ResourcefulDefenseLeaveEffect() {
super(Outcome.Benefit);
staticText = "put those counters on target permanent you control";
}
private ResourcefulDefenseLeaveEffect(final ResourcefulDefenseLeaveEffect effect) {
super(effect);
}
@Override
public ResourcefulDefenseLeaveEffect copy() {
return new ResourcefulDefenseLeaveEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source));
if (permanent == null) {
return false;
}
Counters counters = (Counters) this.getValue("counters");
counters.values()
.stream().filter(counter -> counter.getCount() > 0)
.forEach(counter -> permanent.addCounters(counter, source.getControllerId(), source, game));
return true;
}
}

View file

@ -0,0 +1,53 @@
package mage.cards.r;
import mage.abilities.Mode;
import mage.abilities.effects.common.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.TargetController;
import mage.filter.FilterPermanent;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class RiveteersConfluence extends CardImpl {
private static final FilterPermanent damageFilter = new FilterCreatureOrPlaneswalkerPermanent();
static {
damageFilter.add(TargetController.NOT_YOU.getControllerPredicate());
}
public RiveteersConfluence(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}{R}{G}");
// Choose three. You may choose the same mode more than once.
this.getSpellAbility().getModes().setMinModes(3);
this.getSpellAbility().getModes().setMaxModes(3);
this.getSpellAbility().getModes().setEachModeMoreThanOnce(true);
// You draw a card and you lose 1 life.
this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).setText("you draw a card"));
this.getSpellAbility().addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and"));
// Riveteers Confluence deals 1 damage to each creature and planeswalker you dont control.
this.getSpellAbility().addMode(new Mode(new DamageAllEffect(1, damageFilter)));
// You may put a land card from your hand or graveyard onto the battlefield tapped.
this.getSpellAbility().addMode(new Mode(new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(StaticFilters.FILTER_CARD_LAND_A, true)));
}
private RiveteersConfluence(final RiveteersConfluence card) {
super(card);
}
@Override
public RiveteersConfluence copy() {
return new RiveteersConfluence(this);
}
}

View file

@ -17,7 +17,7 @@ public final class RobTheArchives extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}");
// Casualty 1
this.addAbility(new CasualtyAbility(this, 1));
this.addAbility(new CasualtyAbility(1));
// Exile the top two cards of your library. You may play those cards this turn.
this.getSpellAbility().addEffect(new ExileTopXMayPlayUntilEndOfTurnEffect(2, false));

View file

@ -20,7 +20,7 @@ public final class RooftopNuisance extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{U}");
// Casualty 1
this.addAbility(new CasualtyAbility(this, 1));
this.addAbility(new CasualtyAbility(1));
// Tap target creature. It doesn't untap during its controller's next untap step.
this.getSpellAbility().addEffect(new TapTargetEffect());

View file

@ -27,8 +27,8 @@ import java.util.UUID;
*/
public final class ShareTheSpoils extends CardImpl {
public ShareTheSpoils(UUID ownderId, CardSetInfo setInfo) {
super(ownderId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}");
public ShareTheSpoils(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}");
// When Share the Spoils enters the battlefield or an opponent loses the game,
// exile the top card of each players library.exile the top card of each players library.

View file

@ -0,0 +1,63 @@
package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.condition.common.TargetHasCounterCondition;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.continuous.GainControlTargetEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.SubType;
import mage.constants.TargetController;
import mage.counters.CounterType;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicates;
import mage.filter.predicate.mageobject.CommanderPredicate;
import mage.game.Game;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class ShieldBroker extends CardImpl {
private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("noncommander creature you don't control");
static {
filter.add(Predicates.not(CommanderPredicate.instance));
filter.add(TargetController.NOT_YOU.getControllerPredicate());
}
public ShieldBroker(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}{U}");
this.addSubType(SubType.CEPHALID, SubType.ADVISOR);
this.power = new MageInt(3);
this.toughness = new MageInt(4);
// When Shield Broker enters the battlefield, put a shield counter on target noncommander creature you dont control.
// You gain control of that creature for as long as it has a shield counter on it.
// (If it would be dealt damage or destroyed, remove a shield counter from it instead.)
Ability etbAbility = new EntersBattlefieldTriggeredAbility(new AddCountersTargetEffect(CounterType.SHIELD.createInstance()));
Effect gainControlEffect = new GainControlTargetEffect(Duration.Custom, false, null, new TargetHasCounterCondition(CounterType.SHIELD));
gainControlEffect.setText("You gain control of that creature for as long as it has a shield counter on it. " +
"<i>(If it would be dealt damage or destroyed, remove a shield counter from it instead.)</i>");
etbAbility.addEffect(gainControlEffect);
etbAbility.addTarget(new TargetCreaturePermanent(filter));
this.addAbility(etbAbility);
}
private ShieldBroker(final ShieldBroker card) {
super(card);
}
@Override
public ShieldBroker copy() {
return new ShieldBroker(this);
}
}

View file

@ -0,0 +1,126 @@
package mage.cards.s;
import mage.MageInt;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.costs.common.ExileSourceFromGraveCost;
import mage.abilities.dynamicvalue.common.StaticValue;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.ExileTargetEffect;
import mage.abilities.effects.common.continuous.GainSuspendEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.SuspendAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class SinisterConcierge extends CardImpl {
public SinisterConcierge(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{U}");
this.addSubType(SubType.HUMAN, SubType.WIZARD);
this.power = new MageInt(2);
this.toughness = new MageInt(1);
// When Sinister Concierge dies, you may exile it and put three time counters on it.
// If you do, exile up to one target creature and put three time counters on it.
// Each card exiled this way that doesn't have suspend gains suspend.
// (For each card with suspend, its owner removes a time counter from it at the beginning of their upkeep.
// When the last is removed, they cast it without paying its mana cost. Those creature spells have haste.)
Ability ability = new DiesSourceTriggeredAbility(
new DoIfCostPaid(
new SinisterConciergeEffect(),
new ExileSourceFromGraveCost()
).setText("you may exile it and put three time counters on it")
);
ability.addTarget(new TargetCreaturePermanent(0, 1));
this.addAbility(ability);
}
private SinisterConcierge(final SinisterConcierge card) {
super(card);
}
@Override
public SinisterConcierge copy() {
return new SinisterConcierge(this);
}
}
class SinisterConciergeEffect extends OneShotEffect {
public SinisterConciergeEffect() {
super(Outcome.Removal);
this.staticText = "you may exile it and put three time counters on it. " +
"If you do, exile up to one target creature and put three time counters on it. " +
"Each card exiled this way that doesn't have suspend gains suspend. " +
"<i>(For each card with suspend, its owner removes a time counter from it at the beginning of their upkeep. " +
"When the last is removed, they cast it without paying its mana cost. Those creature spells have haste.)</i>";
}
private SinisterConciergeEffect(final SinisterConciergeEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Card card = game.getCard(source.getSourceId());
Permanent targetCreature = game.getPermanent(this.getTargetPointer().getFirst(game, source));
if (controller == null || card == null || targetCreature == null) {
return false;
}
// Put the time counters on the Sinister Concierge and give it Suspend
if (game.getState().getZone(card.getId()) == Zone.EXILED) {
Effect addCountersSourceEffect = new AddCountersSourceEffect(CounterType.TIME.createInstance(), StaticValue.get(3), false ,true);
boolean sourceCardShouldGetSuspend = addCountersSourceEffect.apply(game, source);
if (sourceCardShouldGetSuspend && !card.getAbilities(game).containsClass(SuspendAbility.class)) {
game.addEffect(new GainSuspendEffect(new MageObjectReference(card, game)), source);
}
}
// Exile, put time counters, and give suspend for target
Effect exileTarget = new ExileTargetEffect();
exileTarget.setTargetPointer(this.getTargetPointer());
if (exileTarget.apply(game, source)) {
Effect addCountersTargetEffect = new AddCountersTargetEffect(CounterType.TIME.createInstance(3));
addCountersTargetEffect.setTargetPointer(this.getTargetPointer());
boolean targetCardShouldGetSuspend = addCountersTargetEffect.apply(game, source);
if (targetCardShouldGetSuspend && !targetCreature.getAbilities(game).containsClass(SuspendAbility.class)) {
Card targetCard = game.getCard(getTargetPointer().getFirst(game, source));
if (!targetCard.getAbilities(game).containsClass(SuspendAbility.class)) {
game.addEffect(new GainSuspendEffect(new MageObjectReference(targetCard, game)), source);
}
}
}
return true;
}
@Override
public SinisterConciergeEffect copy() {
return new SinisterConciergeEffect(this);
}
}

View file

@ -0,0 +1,127 @@
package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.common.EscapesWithAbility;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.keyword.EscapeAbility;
import mage.abilities.keyword.FlyingAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.FilterCard;
import mage.filter.predicate.Predicates;
import mage.game.ExileZone;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInExile;
import mage.util.CardUtil;
import java.util.Set;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class SkywayRobber extends CardImpl {
public SkywayRobber(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{U}");
this.addSubType(SubType.BIRD, SubType.ROGUE);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Flying
this.addAbility(FlyingAbility.getInstance());
// Escape{3}{U}, Exile five other cards from your graveyard. (You may cast this card from your graveyard for its escape cost.)
this.addAbility(new EscapeAbility(this, "{3}{U}", 5));
// Skyway Robber escapes with Whenever Skyway Robber deals combat damage to a player, you may cast an artifact, instant, or sorcery spell from among cards exiled with Skyway Robber without paying its mana cost.
// NOTE: Optional is handled by the effect, this way it won't prompt the player if no valid cards are available
TriggeredAbility dealsDamageAbility = new DealsCombatDamageToAPlayerTriggeredAbility(new SkywayRobberCastForFreeEffect(), false);
this.addAbility(new EscapesWithAbility(0, dealsDamageAbility));
}
private SkywayRobber(final SkywayRobber card) {
super(card);
}
@Override
public SkywayRobber copy() {
return new SkywayRobber(this);
}
}
class SkywayRobberCastForFreeEffect extends OneShotEffect {
private static final FilterCard filter = new FilterCard("an artifact, instant, or sorcery card");
static {
filter.add(Predicates.or(
CardType.ARTIFACT.getPredicate(),
CardType.INSTANT.getPredicate(),
CardType.SORCERY.getPredicate())
);
}
public SkywayRobberCastForFreeEffect() {
super(Outcome.PlayForFree);
this.staticText = "you may cast an artifact, instant, or sorcery spell from among cards exiled with Skyway Robber without paying its mana cost";
}
private SkywayRobberCastForFreeEffect(final SkywayRobberCastForFreeEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
String exileZoneName = CardUtil.getObjectZoneString(CardUtil.SOURCE_EXILE_ZONE_TEXT, source.getSourceId(), game, source.getSourceObjectZoneChangeCounter()-1, false);
UUID exileId = CardUtil.getExileZoneId(exileZoneName, game);
ExileZone exileZone = game.getExile().getExileZone(exileId);
if (exileZone == null) {
return false;
}
Set<Card> possibleCards = exileZone.getCards(filter, game);
if (possibleCards.isEmpty()) {
return false;
}
boolean choseToPlay = controller.chooseUse(
Outcome.PlayForFree,
"Cast an artifact, instant, or sorcery spell from among cards exiled with Skyway Robber without paying its mana cost?",
source,
game);
if (!choseToPlay) {
return false;
}
TargetCardInExile target = new TargetCardInExile(filter, exileId);
if (!controller.chooseTarget(Outcome.PlayForFree, target, source, game)) {
return false;
}
Card chosenCard = game.getCard(target.getFirstTarget());
if (chosenCard == null) {
return false;
}
return CardUtil.castSpellWithAttributesForFree(controller, source, game, chosenCard);
}
@Override
public SkywayRobberCastForFreeEffect copy() {
return new SkywayRobberCastForFreeEffect(this);
}
}

View file

@ -0,0 +1,91 @@
package mage.cards.s;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.HideawayPlayEffect;
import mage.abilities.effects.common.ReturnToHandSourceEffect;
import mage.abilities.keyword.CrewAbility;
import mage.abilities.keyword.HideawayAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class SmugglersBuggy extends CardImpl {
public SmugglersBuggy(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{4}");
this.addSubType(SubType.VEHICLE);
this.power = new MageInt(5);
this.toughness = new MageInt(5);
// Hideaway 4
// (When this artifact enters the battlefield, look at the top four cards of your library,
// exile one face down, then put the rest on the bottom in a random order.)
this.addAbility(new HideawayAbility(4));
// Whenever Smugglers Buggy deals combat damage to a player, you may cast the exiled card without paying its mana cost.
// If you do, return Smugglers Buggy to its owners hand.
this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new SmugglersBuggyCastAndReturnEffect(), true));
// Crew 2
this.addAbility(new CrewAbility(2));
}
private SmugglersBuggy(final SmugglersBuggy card) {
super(card);
}
@Override
public SmugglersBuggy copy() {
return new SmugglersBuggy(this);
}
}
class SmugglersBuggyCastAndReturnEffect extends OneShotEffect {
SmugglersBuggyCastAndReturnEffect() {
super(Outcome.Benefit);
this.staticText = "you may cast the exiled card without paying its mana cost. " +
"If you do, return Smuggler's Buggy to its owner's hand";
}
private SmugglersBuggyCastAndReturnEffect(final SmugglersBuggyCastAndReturnEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent smugglersBuggy = game.getPermanent(source.getSourceId());
if (controller == null || smugglersBuggy == null) {
return false;
}
Effect hideawayPlayEffect = new HideawayPlayEffect();
if (!hideawayPlayEffect.apply(game, source)) {
return false;
}
Effect returnToHandEffect = new ReturnToHandSourceEffect();
return returnToHandEffect.apply(game, source);
}
@Override
public Effect copy() {
return new SmugglersBuggyCastAndReturnEffect(this);
}
}

View file

@ -1,15 +1,9 @@
package mage.cards.s;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetAnyTarget;
import mage.target.common.TargetControlledCreaturePermanent;
@ -23,9 +17,8 @@ public final class SoulsFire extends CardImpl {
public SoulsFire(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{R}");
// Target creature you control on the battlefield deals damage equal to its power to any target.
this.getSpellAbility().addEffect(new SoulsFireEffect());
this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect());
this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent());
this.getSpellAbility().addTarget(new TargetAnyTarget());
}
@ -39,48 +32,3 @@ public final class SoulsFire extends CardImpl {
return new SoulsFire(this);
}
}
class SoulsFireEffect extends OneShotEffect {
public SoulsFireEffect() {
super(Outcome.Damage);
this.staticText = "Target creature you control deals damage equal to its power to any target";
}
public SoulsFireEffect(final SoulsFireEffect effect) {
super(effect);
}
@Override
public SoulsFireEffect copy() {
return new SoulsFireEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent sourcePermanent = game.getPermanent(source.getFirstTarget());
if (sourcePermanent == null) {
sourcePermanent = (Permanent) game.getLastKnownInformation(source.getSourceId(), Zone.BATTLEFIELD);
}
if (sourcePermanent == null) {
return false;
}
UUID targetId = source.getTargets().get(1).getFirstTarget();
int damage = sourcePermanent.getPower().getValue();
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
permanent.damage(damage, sourcePermanent.getId(), source, game, false, true);
return true;
}
Player player = game.getPlayer(targetId);
if (player != null) {
player.damage(damage, sourcePermanent.getId(), source, game);
return true;
}
return false;
}
}

View file

@ -10,11 +10,11 @@ import mage.abilities.common.delayed.AtTheBeginOfNextEndStepDelayedTriggeredAbil
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.PutCardFromOneOfTwoZonesOntoBattlefieldEffect;
import mage.abilities.effects.common.ReturnToHandTargetEffect;
import mage.abilities.effects.common.continuous.GainAbilityTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HasteAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
@ -22,15 +22,10 @@ import mage.constants.SubType;
import mage.constants.ComparisonType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.predicate.mageobject.ManaValuePredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInHand;
import mage.target.common.TargetCardInYourGraveyard;
import mage.target.targetpointer.FixedTarget;
/**
@ -39,6 +34,13 @@ import mage.target.targetpointer.FixedTarget;
*/
public final class SwiftWarkite extends CardImpl {
private static final FilterCard filter = new FilterCard("creature card with mana value 3 or less from your hand or graveyard");
static {
filter.add(CardType.CREATURE.getPredicate());
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4));
}
public SwiftWarkite(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{4}{B}{R}");
this.subtype.add(SubType.DRAGON);
@ -49,8 +51,10 @@ public final class SwiftWarkite extends CardImpl {
this.addAbility(FlyingAbility.getInstance());
// When Swift Warkite enters the battlefield, you may put a creature card with converted mana cost 3 or less from your hand or graveyard onto the battlefield. That creature gains haste. Return it to your hand at the beginning of the next end step.
this.addAbility(new EntersBattlefieldTriggeredAbility(new SwiftWarkiteEffect(), true));
this.addAbility(new EntersBattlefieldTriggeredAbility(
new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(filter, false, new SwiftWarkiteEffect()),
true)
);
}
private SwiftWarkite(final SwiftWarkite card) {
@ -65,16 +69,9 @@ public final class SwiftWarkite extends CardImpl {
class SwiftWarkiteEffect extends OneShotEffect {
private static final FilterCard filter = new FilterCard("creature card with mana value 3 or less from your hand or graveyard");
static {
filter.add(CardType.CREATURE.getPredicate());
filter.add(new ManaValuePredicate(ComparisonType.FEWER_THAN, 4));
}
SwiftWarkiteEffect() {
super(Outcome.PutCardInPlay);
this.staticText = "you may put a creature card with mana value 3 or less from your hand or graveyard onto the battlefield. That creature gains haste. Return it to your hand at the beginning of the next end step";
super(Outcome.AddAbility);
this.staticText = "that creature gains haste. Return it to your hand at the beginning of the next end step";
}
SwiftWarkiteEffect(final SwiftWarkiteEffect effect) {
@ -88,46 +85,19 @@ class SwiftWarkiteEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
if (controller.chooseUse(Outcome.PutCardInPlay, "Put a creature card from your hand? (No = from your graveyard)", source, game)) {
Target target = new TargetCardInHand(0, 1, filter);
controller.choose(outcome, target, source, game);
Card card = controller.getHand().get(target.getFirstTarget(), game);
if (card != null) {
if (controller.moveCards(card, Zone.BATTLEFIELD, source, game)) {
Permanent creature = game.getPermanent(card.getId());
if (creature != null) {
ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom);
effect.setTargetPointer(new FixedTarget(creature.getId(), creature.getZoneChangeCounter(game)));
game.addEffect(effect, source);
Effect effect2 = new ReturnToHandTargetEffect();
effect2.setTargetPointer(new FixedTarget(creature.getId(), creature.getZoneChangeCounter(game)));
DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect2);
game.addDelayedTriggeredAbility(delayedAbility, source);
}
}
}
} else {
Target target = new TargetCardInYourGraveyard(0, 1, filter);
target.choose(Outcome.PutCardInPlay, source.getControllerId(), source.getSourceId(), source, game);
Card card = controller.getGraveyard().get(target.getFirstTarget(), game);
if (card != null) {
controller.moveCards(card, Zone.BATTLEFIELD, source, game);
Permanent creature = game.getPermanent(card.getId());
if (creature != null) {
ContinuousEffect effect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom);
effect.setTargetPointer(new FixedTarget(creature.getId(), creature.getZoneChangeCounter(game)));
game.addEffect(effect, source);
Effect effect2 = new ReturnToHandTargetEffect();
effect2.setTargetPointer(new FixedTarget(creature.getId(), creature.getZoneChangeCounter(game)));
DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect2);
game.addDelayedTriggeredAbility(delayedAbility, source);
}
}
}
return true;
Permanent movedCreature = game.getPermanent(getTargetPointer().getFirst(game, source));
if (movedCreature == null) {
return false;
}
return false;
ContinuousEffect gainHasteEffect = new GainAbilityTargetEffect(HasteAbility.getInstance(), Duration.Custom);
gainHasteEffect.setTargetPointer(new FixedTarget(movedCreature.getId(), movedCreature.getZoneChangeCounter(game)));
game.addEffect(gainHasteEffect, source);
Effect returnToHandEffect = new ReturnToHandTargetEffect();
returnToHandEffect.setTargetPointer(new FixedTarget(movedCreature.getId(), movedCreature.getZoneChangeCounter(game)));
DelayedTriggeredAbility delayedAbility = new AtTheBeginOfNextEndStepDelayedTriggeredAbility(returnToHandEffect);
game.addDelayedTriggeredAbility(delayedAbility, source);
return true;
}
}

View file

@ -0,0 +1,136 @@
package mage.cards.s;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.CounterTargetEffect;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.CardsImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.players.Library;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class SwindlersScheme extends CardImpl {
public SwindlersScheme(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}");
// Whenever an opponent casts a spell from their hand, you may reveal the top card of your library.
// If it shares a card type with that spell, counter that spell and that opponent may cast the revealed card without paying its mana cost.
this.addAbility(new SwindlersSchemeOpponentCastTriggeredAbility());
}
private SwindlersScheme(final SwindlersScheme card) {
super(card);
}
@Override
public SwindlersScheme copy() {
return new SwindlersScheme(this);
}
}
/**
* TODO: Creating a custom ability since SpellCastOpponentTriggeredAbility is getting out of hand and needs
* to be refactored to not use telescoping constructors.
*/
class SwindlersSchemeOpponentCastTriggeredAbility extends TriggeredAbilityImpl {
SwindlersSchemeOpponentCastTriggeredAbility() {
super(Zone.BATTLEFIELD, new SwindlersSchemeEffect(), true);
setTriggerPhrase("Whenever an opponent casts a spell from their hand, ");
}
private SwindlersSchemeOpponentCastTriggeredAbility(final SwindlersSchemeOpponentCastTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.SPELL_CAST;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Player controller = game.getPlayer(this.getControllerId());
if (controller == null || !controller.hasOpponent(event.getPlayerId(), game)) {
return false;
}
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell == null || spell.getFromZone() != Zone.HAND) {
return false;
}
getEffects().setValue("spellCast", spell);
return true;
}
@Override
public SwindlersSchemeOpponentCastTriggeredAbility copy() {
return new SwindlersSchemeOpponentCastTriggeredAbility(this);
}
}
class SwindlersSchemeEffect extends OneShotEffect {
SwindlersSchemeEffect() {
super(Outcome.Detriment);
this.staticText = "reveal the top card of your library. " +
"If it shares a card type with that spell, counter that spell and that opponent may cast the revealed card without paying its mana cost.";
}
private SwindlersSchemeEffect(final SwindlersSchemeEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Spell spell = (Spell) getValue("spellCast");
Library library = controller.getLibrary();
Card cardFromTop = library.getFromTop(game);
if (cardFromTop == null) {
return false;
}
if (cardFromTop.getCardType(game).stream().noneMatch(spell.getCardType(game)::contains)) {
return false;
}
Player opponent = game.getPlayer(spell.getControllerId());
Effect counterEffect = new CounterTargetEffect();
counterEffect.setTargetPointer(new FixedTarget(spell.getId()));
counterEffect.apply(game, source);
CardUtil.castSpellWithAttributesForFree(opponent, source, game, cardFromTop);
return true;
}
@Override
public SwindlersSchemeEffect copy() {
return new SwindlersSchemeEffect(this);
}
}

View file

@ -0,0 +1,188 @@
package mage.cards.s;
import mage.ApprovingObject;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.BeginningOfEndStepTriggeredAbility;
import mage.abilities.common.DiesCreatureTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect;
import mage.abilities.keyword.FlyingAbility;
import mage.abilities.keyword.HasteAbility;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.FilterPermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.AnotherPredicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.players.Player;
import mage.target.TargetPermanent;
import mage.target.common.TargetAnyTarget;
import mage.watchers.Watcher;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class SyrixCarrierOfTheFlame extends CardImpl {
private static final String description = "Phoenix you control";
private static final FilterPermanent anotherPhoenixFilter = new FilterControlledPermanent("another Phoenix you control");
private static final FilterPermanent phoenixFilter = new FilterControlledPermanent(description);
static {
anotherPhoenixFilter.add(AnotherPredicate.instance);
anotherPhoenixFilter.add(SubType.PHOENIX.getPredicate());
phoenixFilter.add(SubType.PHOENIX.getPredicate());
}
public SyrixCarrierOfTheFlame(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}");
this.addSuperType(SuperType.LEGENDARY);
this.addSubType(SubType.PHOENIX);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Flying, haste
this.addAbility(FlyingAbility.getInstance());
this.addAbility(HasteAbility.getInstance());
// At the beginning of each end step, if a creature card left your graveyard this turn,
// target Phoenix you control deals damage equal to its power to any target.
BeginningOfEndStepTriggeredAbility ability = new BeginningOfEndStepTriggeredAbility(
new DamageWithPowerFromOneToAnotherTargetEffect(),
TargetController.EACH_PLAYER,
SyrixCarrierOfTheFlameCondition.instance,
false
);
ability.addTarget(new TargetPermanent(phoenixFilter));
ability.addTarget(new TargetAnyTarget());
ability.addWatcher(new SyrixCarrierOfTheFlameWatcher());
this.addAbility(ability);
// Whenever another Phoenix you control dies, you may cast Syrix, Carrier of the Flame from your graveyard.
this.addAbility(new DiesCreatureTriggeredAbility(
Zone.GRAVEYARD,
new SyrixCarrierOfTheFlameCastEffect(),
true,
anotherPhoenixFilter,
false)
);
}
private SyrixCarrierOfTheFlame(final SyrixCarrierOfTheFlame card) {
super(card);
}
@Override
public SyrixCarrierOfTheFlame copy() {
return new SyrixCarrierOfTheFlame(this);
}
}
/**
* Based on Harness the Storm
*/
class SyrixCarrierOfTheFlameCastEffect extends OneShotEffect {
SyrixCarrierOfTheFlameCastEffect() {
super(Outcome.Benefit);
this.staticText = "you may cast {this} from your graveyard";
}
SyrixCarrierOfTheFlameCastEffect(final SyrixCarrierOfTheFlameCastEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Card card = game.getCard(source.getSourceId());
if (card == null) {
return false;
}
if (controller.chooseUse(Outcome.Benefit, "Cast " + card.getIdName() + " from your graveyard?", source, game)) {
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), Boolean.TRUE);
controller.cast(controller.chooseAbilityForCast(card, game, false),
game, false, new ApprovingObject(source, game));
game.getState().setValue("PlayFromNotOwnHandZone" + card.getId(), null);
}
return true;
}
@Override
public SyrixCarrierOfTheFlameCastEffect copy() {
return new SyrixCarrierOfTheFlameCastEffect(this);
}
}
/**
* Creature card left your graveyard this turn
*/
enum SyrixCarrierOfTheFlameCondition implements Condition {
instance;
private static final String string = "a creature card left your graveyard this turn";
@Override
public boolean apply(Game game, Ability source) {
SyrixCarrierOfTheFlameWatcher watcher = game.getState().getWatcher(SyrixCarrierOfTheFlameWatcher.class);
return watcher != null && watcher.hadACreatureLeave(source.getControllerId());
}
@Override
public String toString() {
return string;
}
}
/**
* Creature card left your graveyard this turn
*/
class SyrixCarrierOfTheFlameWatcher extends Watcher {
// Player IDs who had a creature card leave their graveyard
private final Set<UUID> creatureCardLeftPlayerIds = new HashSet<>();
SyrixCarrierOfTheFlameWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
if (!(event.getType() == GameEvent.EventType.ZONE_CHANGE && event instanceof ZoneChangeEvent)) {
return;
}
ZoneChangeEvent zoneChangeEvent = (ZoneChangeEvent) event;
if (zoneChangeEvent.getFromZone() != Zone.GRAVEYARD) {
return;
}
Card card = zoneChangeEvent.getTarget();
if (card != null && card.isCreature(game)) {
creatureCardLeftPlayerIds.add(card.getOwnerId());
}
}
public boolean hadACreatureLeave(UUID playerId) {
return creatureCardLeftPlayerIds.contains(playerId);
}
@Override
public void reset() {
super.reset();
creatureCardLeftPlayerIds.clear();
}
}

View file

@ -0,0 +1,122 @@
package mage.cards.t;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.BeginningOfEndStepTriggeredAbility;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.*;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.TargetPlayer;
import mage.target.common.TargetOpponent;
import java.util.Set;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class TenuousTruce extends CardImpl {
public TenuousTruce(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}");
this.addSubType(SubType.AURA);
// Enchant opponent
TargetPlayer targetOpponent = new TargetOpponent();
this.getSpellAbility().addTarget(targetOpponent);
this.getSpellAbility().addEffect(new AttachEffect(Outcome.DrawCard));
this.addAbility(new EnchantAbility(targetOpponent.getTargetName()));
// At the beginning of enchanted opponents end step, you and that player each draw a card.
Ability drawAbility = new BeginningOfEndStepTriggeredAbility(
new DrawCardSourceControllerEffect(1).setText("you "),
TargetController.ENCHANTED,
false);
Effect enchantedPlayerDrawEffect = new DrawCardTargetEffect(1);
enchantedPlayerDrawEffect.concatBy("and").setText("that player each draw a card");
drawAbility.addEffect(enchantedPlayerDrawEffect);
this.addAbility(drawAbility);
// When you attack enchanted opponent or a planeswalker they control
// or when they attack you or a planeswalker you control,
// sacrifice Tenuous Truce.
this.addAbility(new TenuousTruceAttackTriggeredAbility());
}
private TenuousTruce(final TenuousTruce card) {
super(card);
}
@Override
public TenuousTruce copy() {
return new TenuousTruce(this);
}
}
class TenuousTruceAttackTriggeredAbility extends TriggeredAbilityImpl {
TenuousTruceAttackTriggeredAbility() {
super(Zone.BATTLEFIELD, new SacrificeSourceEffect(), false);
setTriggerPhrase("When you attack enchanted opponent or a planeswalker they control " +
"or when they attack you or a planeswalker you control, ");
}
TenuousTruceAttackTriggeredAbility(final TenuousTruceAttackTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARED_ATTACKERS;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Permanent tenuousTruce = game.getPermanent(this.getSourceId());
Player controller = game.getPlayer(this.getControllerId());
Player attacker = game.getPlayer(game.getCombat().getAttackingPlayerId());
if (tenuousTruce == null || controller == null || attacker == null) {
return false;
}
Player enchantedPlayer = game.getPlayer(tenuousTruce.getAttachedTo());
if (enchantedPlayer == null) {
return false;
}
Set<UUID> defenderIds = game.getCombat().getDefenders();
if (controller.equals(attacker)) {
return TenuousTruceAttackTriggeredAbility.playerOneAttackingPlayerBOrTheirPlaneswalker(controller.getId(), enchantedPlayer.getId(), defenderIds, game);
} else if (enchantedPlayer.equals(attacker)) {
return TenuousTruceAttackTriggeredAbility.playerOneAttackingPlayerBOrTheirPlaneswalker(enchantedPlayer.getId(), controller.getId(), defenderIds, game);
} else {
return false;
}
}
private static boolean playerOneAttackingPlayerBOrTheirPlaneswalker(UUID playerAId, UUID playerBId, Set<UUID> defenderIds, Game game) {
if (defenderIds.contains(playerBId)) {
return true;
}
// Check planeswalkers
for (UUID defenderId : defenderIds) {
Permanent perm = game.getPermanent(defenderId);
if (perm != null && perm.getOwnerId().equals(playerBId)) {
return true;
}
}
return false;
}
@Override
public TenuousTruceAttackTriggeredAbility copy() {
return new TenuousTruceAttackTriggeredAbility(this);
}
}

View file

@ -4,6 +4,7 @@ import mage.abilities.Ability;
import mage.abilities.common.BeginningOfCombatTriggeredAbility;
import mage.abilities.common.LeavesBattlefieldAllTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.condition.common.SourceHasCountersCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.OneShotEffect;
import mage.cards.CardImpl;
@ -40,7 +41,7 @@ public final class TheOzolith extends CardImpl {
Ability ability = new ConditionalInterveningIfTriggeredAbility(
new BeginningOfCombatTriggeredAbility(
new TheOzolithMoveCountersEffect(), TargetController.YOU, true
), TheOzolithCondition.instance, "At the beginning of combat on your turn, " +
), SourceHasCountersCondition.instance, "At the beginning of combat on your turn, " +
"if {this} has counters on it, you may move all counters from {this} onto target creature."
);
ability.addTarget(new TargetCreaturePermanent());
@ -118,32 +119,11 @@ class TheOzolithLeaveEffect extends OneShotEffect {
return false;
}
counters.values()
.stream()
.forEach(counter -> permanent.addCounters(counter, source.getControllerId(), source, game));
return true;
}
}
enum TheOzolithCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent == null) {
return false;
}
return permanent != null
&& permanent
.getCounters(game)
.values()
.stream()
.mapToInt(Counter::getCount)
.max()
.orElse(0) > 0;
}
}
class TheOzolithMoveCountersEffect extends OneShotEffect {
TheOzolithMoveCountersEffect() {

View file

@ -0,0 +1,81 @@
package mage.cards.t;
import mage.MageObject;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.common.continuous.EachSpellYouCastHasReplicateEffect;
import mage.abilities.effects.keyword.ScryEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.cards.ModalDoubleFacesCardHalf;
import mage.cards.SplitCardHalf;
import mage.constants.CardType;
import mage.constants.Zone;
import mage.filter.FilterSpell;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class ThreefoldSignal extends CardImpl {
private static final FilterSpell filter = new FilterSpell("spell you cast that's exactly three colors");
static {
filter.add(ThreeColorPredicate.instance);
}
public ThreefoldSignal(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
// When Threefold Signal enters the battlefield, scry 3.
this.addAbility(new EntersBattlefieldTriggeredAbility(new ScryEffect(3)));
// Each spell you cast thats exactly three colors has replicate {3}.
// (When you cast it, copy it for each time you paid its replicate cost.
// You may choose new targets for the copies.
// A copy of a permanent spell becomes a token.)
this.addAbility(new SimpleStaticAbility(new EachSpellYouCastHasReplicateEffect(filter, new GenericManaCost(3))));
}
private ThreefoldSignal(final ThreefoldSignal card) {
super(card);
}
@Override
public ThreefoldSignal copy() {
return new ThreefoldSignal(this);
}
}
/**
* Based on MultiColorPredicate
*/
enum ThreeColorPredicate implements Predicate<MageObject> {
instance;
@Override
public boolean apply(MageObject input, Game game) {
// 708.3. Each split card that consists of two halves with different colored mana symbols in their mana costs
// is a multicolored card while it's not a spell on the stack. While it's a spell on the stack, it's only the
// color or colors of the half or halves being cast. #
if (input instanceof SplitCardHalf
&& game.getState().getZone(input.getId()) != Zone.STACK) {
return 3 == ((SplitCardHalf) input).getMainCard().getColor(game).getColorCount();
} else if (input instanceof ModalDoubleFacesCardHalf
&& (game.getState().getZone(input.getId()) != Zone.STACK && game.getState().getZone(input.getId()) != Zone.BATTLEFIELD)) {
// While a double-faced card isnt on the stack or battlefield, consider only the characteristics of its front face.
return 3 == ((ModalDoubleFacesCardHalf) input).getMainCard().getColor(game).getColorCount();
} else {
return 3 == input.getColor(game).getColorCount();
}
}
@Override
public String toString() {
return "Multicolored";
}
}

View file

@ -114,6 +114,7 @@ class TurfWarTriggeredAbility extends TriggeredAbilityImpl {
public TurfWarTriggeredAbility() {
super(Zone.BATTLEFIELD, new TurfWarControlEffect());
setTriggerPhrase("Whenever a creature deals combat damage to a player, if that player controls one or more lands with contested counters on them, ");
}
private TurfWarTriggeredAbility(final TurfWarTriggeredAbility ability) {
@ -155,11 +156,6 @@ class TurfWarTriggeredAbility extends TriggeredAbilityImpl {
}
return false;
}
@Override
public String getTriggerPhrase() {
return "Whenever a creature deals combat damage to a player, if that player controls one or more lands with contested counters on them, ";
}
}
class TurfWarControlEffect extends OneShotEffect {

View file

@ -2,6 +2,7 @@ package mage.cards.u;
import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.TargetOfOpponentsSpellOrAbilityTriggeredAbility;
import mage.abilities.costs.mana.GenericManaCost;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CounterUnlessPaysEffect;
@ -33,8 +34,9 @@ public final class UnsettledMariner extends CardImpl {
// Changeling
this.addAbility(new ChangelingAbility());
// Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls, counter that spell or ability unless its controller pays {1}.
this.addAbility(new UnsettledMarinerTriggeredAbility());
// Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls,
// counter that spell or ability unless its controller pays {1}.
this.addAbility(new TargetOfOpponentsSpellOrAbilityTriggeredAbility(new CounterUnlessPaysEffect(new GenericManaCost(1))));
}
private UnsettledMariner(final UnsettledMariner card) {
@ -46,47 +48,3 @@ public final class UnsettledMariner extends CardImpl {
return new UnsettledMariner(this);
}
}
class UnsettledMarinerTriggeredAbility extends TriggeredAbilityImpl {
UnsettledMarinerTriggeredAbility() {
super(Zone.BATTLEFIELD, null);
}
private UnsettledMarinerTriggeredAbility(final UnsettledMarinerTriggeredAbility ability) {
super(ability);
}
@Override
public UnsettledMarinerTriggeredAbility copy() {
return new UnsettledMarinerTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.TARGETED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (!game.getOpponents(getControllerId()).contains(event.getPlayerId())) {
return false;
}
Permanent permanent = game.getPermanent(event.getTargetId());
if ((permanent == null || !permanent.getControllerId().equals(getControllerId()))
&& !event.getTargetId().equals(getControllerId())) {
return false;
}
Effect effect = new CounterUnlessPaysEffect(new GenericManaCost(1));
effect.setTargetPointer(new FixedTarget(event.getSourceId(), game));
this.getEffects().clear();
this.addEffect(effect);
return true;
}
@Override
public String getRule() {
return "Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls, " +
"counter that spell or ability unless its controller pays {1}.";
}
}

View file

@ -0,0 +1,132 @@
package mage.cards.v;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.TriggeredAbility;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.common.SimpleActivatedAbility;
import mage.abilities.condition.common.TreasureSpentToCastCondition;
import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.dynamicvalue.DynamicValue;
import mage.abilities.effects.Effect;
import mage.abilities.effects.common.CreateTokenTargetEffect;
import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.HasteAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.token.TreasureToken;
import mage.players.Player;
import mage.target.common.TargetOpponent;
import mage.watchers.common.CreatedTokenWatcher;
import mage.watchers.common.ManaPaidSourceWatcher;
import java.util.Optional;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class VaziKeenNegotiator extends CardImpl {
public VaziKeenNegotiator(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{R}{G}");
this.addSuperType(SuperType.LEGENDARY);
this.addSubType(SubType.HUMAN, SubType.ADVISOR);
this.power = new MageInt(3);
this.toughness = new MageInt(3);
// Haste
this.addAbility(HasteAbility.getInstance());
// {T}: Target opponent creates X Treasure tokens, where X is the number of Treasure tokens you created this turn.
Ability tapAbility = new SimpleActivatedAbility(new CreateTokenTargetEffect(new TreasureToken(), VaziKeenNegotiatorNumberOfTokensCreated.instance), new TapSourceCost());
tapAbility.addTarget(new TargetOpponent());
this.addAbility(tapAbility);
// Whenever an opponent casts a spell or activates an ability,
// if mana from a Treasure was spent to cast it or activate it,
// put a +1/+1 counter on target creature,
// then draw a card.
Ability castAbility = new VaziKeenNegotiatorOpponentCastsOrActivatesTriggeredAbility();
castAbility.addTarget(new TargetOpponent());
castAbility.addEffect(new DrawCardSourceControllerEffect(1));
this.addAbility(castAbility);
}
private VaziKeenNegotiator(final VaziKeenNegotiator card) {
super(card);
}
@Override
public VaziKeenNegotiator copy() {
return new VaziKeenNegotiator(this);
}
}
class VaziKeenNegotiatorOpponentCastsOrActivatesTriggeredAbility extends TriggeredAbilityImpl {
VaziKeenNegotiatorOpponentCastsOrActivatesTriggeredAbility() {
super(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.P1P1.createInstance()));
setTriggerPhrase("Whenever an opponent casts a spell or activates an ability, if mana from a Treasure was spent to cast it or activate it, ");
}
private VaziKeenNegotiatorOpponentCastsOrActivatesTriggeredAbility(final VaziKeenNegotiatorOpponentCastsOrActivatesTriggeredAbility ability) {
super(ability);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.SPELL_CAST
|| event.getType() == GameEvent.EventType.ACTIVATED_ABILITY;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Player controller = game.getPlayer(getControllerId());
Player caster = game.getPlayer(event.getPlayerId());
Optional<Ability> optionalAbility = game.getAbility(event.getTargetId(), this.sourceId);
if (controller == null
|| caster == null
|| !game.getOpponents(controller.getId()).contains(caster.getId())
|| !optionalAbility.isPresent()) {
return false;
}
return TreasureSpentToCastCondition.instance.apply(game, optionalAbility.get());
}
@Override
public TriggeredAbility copy() {
return new VaziKeenNegotiatorOpponentCastsOrActivatesTriggeredAbility(this);
}
}
enum VaziKeenNegotiatorNumberOfTokensCreated implements DynamicValue {
instance;
@Override
public int calculate(Game game, Ability sourceAbility, Effect effect) {
return CreatedTokenWatcher.getTypeCreatedCountByPlayer(sourceAbility.getControllerId(), TreasureToken.class, game);
}
@Override
public DynamicValue copy() {
return instance;
}
@Override
public String getMessage() {
return "the number of Treasure tokens you created this turn";
}
@Override
public String toString() {
return "X";
}
}

View file

@ -0,0 +1,72 @@
package mage.cards.w;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.DiesSourceTriggeredAbility;
import mage.abilities.condition.Condition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.common.ReturnToBattlefieldUnderOwnerControlSourceEffect;
import mage.abilities.keyword.BlitzAbility;
import mage.abilities.keyword.TrampleAbility;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.watchers.common.DamageDoneWatcher;
import java.util.UUID;
/**
* @author Alex-Vasile
*/
public class WaveOfRats extends CardImpl {
public WaveOfRats(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}");
this.addSubType(SubType.RAT);
this.power = new MageInt(4);
this.toughness = new MageInt(2);
// Trample
this.addAbility(TrampleAbility.getInstance());
// When Wave of Rats dies, if it dealt combat damage to a player this turn, return it to the battlefield under its owners control.
this.addAbility(new ConditionalInterveningIfTriggeredAbility(
new DiesSourceTriggeredAbility(new ReturnToBattlefieldUnderOwnerControlSourceEffect()),
WaveOfRatsDealtDamageToPlayerCondition.instance,
"When Wave of Rats dies, if it dealt combat damage to a player this turn, return it to the battlefield under its owner's control.")
);
// Blitz {4}{B} (If you cast this spell for its blitz cost, it gains haste and When this creature dies, draw a card. Sacrifice it at the beginning of the next end step.)
this.addAbility(new BlitzAbility(this, "{4}{B}"));
}
private WaveOfRats(final WaveOfRats card) {
super(card);
}
@Override
public WaveOfRats copy() {
return new WaveOfRats(this);
}
}
enum WaveOfRatsDealtDamageToPlayerCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
DamageDoneWatcher watcher = game.getState().getWatcher(DamageDoneWatcher.class);
Permanent waveOfRats = game.getPermanent(source.getSourceId());
if (watcher == null || waveOfRats == null) {
return false;
}
if (watcher.damageDoneBy(waveOfRats.getId(), waveOfRats.getZoneChangeCounter(game), game) < 1) {
return false;
}
return watcher.damagedAPlayer(waveOfRats.getId(), waveOfRats.getZoneChangeCounter(game), game);
}
}

View file

@ -0,0 +1,234 @@
package mage.cards.w;
import mage.MageInt;
import mage.abilities.Ability;
import mage.abilities.common.AttacksTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.Condition;
import mage.abilities.decorator.ConditionalAsThoughEffect;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ReplacementEffectImpl;
import mage.abilities.effects.common.combat.CanAttackAsThoughItDidntHaveDefenderSourceEffect;
import mage.abilities.effects.common.continuous.BoostSourceEffect;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.abilities.hint.Hint;
import mage.abilities.keyword.*;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.watchers.Watcher;
import java.util.*;
/**
* Based on Backstreet Bruiser, O-Kagachi, and Pramikon
* @author Alex-Vasile
*/
public class WeatheredSentinels extends CardImpl {
public WeatheredSentinels(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}");
addSubType(SubType.WALL);
this.power = new MageInt(2);
this.toughness = new MageInt(5);
// Defender, vigilance, reach, trample
this.addAbility(DefenderAbility.getInstance());
this.addAbility(VigilanceAbility.getInstance());
this.addAbility(ReachAbility.getInstance());
this.addAbility(TrampleAbility.getInstance());
// Weathered Sentinels can attack players who attacked you during their last turn as though it didn't have defender.
this.addAbility(new SimpleStaticAbility(
new ConditionalAsThoughEffect(
new CanAttackAsThoughItDidntHaveDefenderSourceEffect(Duration.WhileOnBattlefield),
WeatheredSentinelsCanAttackSomeoneCondition.instance)
.setText("Weathered Sentinels can attack players who attacked you during their last turn as though it didn't have defender.")),
new WeatheredSentinelsLastTurnAttackersWatcher()
);
// Whenever Weathered Sentinels attacks, it gets +3/+3 and gains indestructible until end of turn.
Ability ability = new AttacksTriggeredAbility(
new BoostSourceEffect(3, 3, Duration.EndOfTurn).setText("it gets +3/+3")
);
ability.addEffect(new GainAbilitySourceEffect(
IndestructibleAbility.getInstance(), Duration.EndOfTurn
).concatBy("and").setText("gains indestructible until end of turn")
);
this.addAbility(ability);
// Ability to limit who Weathered Sentinels can attack
this.addAbility(new SimpleStaticAbility(
new WeatheredSentinelsAttackerReplacementEffect()
).addHint(WeatheredSentinelsPlayersWhoAttackedYouLastTurn.instance)
);
}
private WeatheredSentinels(final WeatheredSentinels card) {
super(card);
}
@Override
public WeatheredSentinels copy() {
return new WeatheredSentinels(this);
}
}
class WeatheredSentinelsAttackerReplacementEffect extends ReplacementEffectImpl {
WeatheredSentinelsAttackerReplacementEffect() {
super(Duration.WhileOnBattlefield, Outcome.Benefit);
}
WeatheredSentinelsAttackerReplacementEffect(final WeatheredSentinelsAttackerReplacementEffect effect) {
super(effect);
}
@Override
public boolean replaceEvent(GameEvent event, Ability source, Game game) {
return true;
}
@Override
public boolean checksEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.DECLARE_ATTACKER;
}
@Override
public boolean applies(GameEvent event, Ability source, Game game) {
Player controller = game.getPlayer(source.getControllerId());
Player attacker = game.getPlayer(event.getPlayerId());
if (controller == null || attacker == null) {
return false;
}
if (!attacker.equals(controller)) {
return false;
}
Player defender;
if (game.getPlayer(event.getTargetId()) != null) {
defender = game.getPlayer(event.getTargetId());
} else {
Permanent planeswalker = game.getPermanent(event.getTargetId());
defender = (planeswalker == null) ? null : game.getPlayer(planeswalker.getControllerId());
}
if (defender == null) {
return false;
}
WeatheredSentinelsLastTurnAttackersWatcher watcher = game.getState().getWatcher(WeatheredSentinelsLastTurnAttackersWatcher.class);
if (watcher == null) {
return false;
}
// Attacker and defender are supposed to be flipped here
return watcher.checkPlayer(defender.getId(), attacker.getId());
}
@Override
public ContinuousEffect copy() {
return new WeatheredSentinelsAttackerReplacementEffect(this);
}
}
enum WeatheredSentinelsPlayersWhoAttackedYouLastTurn implements Hint {
instance;
@Override
public String getText(Game game, Ability ability) {
Player controller = game.getPlayer(ability.getControllerId());
WeatheredSentinelsLastTurnAttackersWatcher watcher = game.getState().getWatcher(WeatheredSentinelsLastTurnAttackersWatcher.class);
if (controller == null || watcher == null) {
return "";
}
StringBuilder stringBuilder = new StringBuilder("Attacked you on their last turn: ");
Iterator<UUID> opponentIdIterator = game.getOpponents(controller.getId()).iterator();
while (opponentIdIterator.hasNext()) {
UUID opponentId = opponentIdIterator.next();
Player opponent = game.getPlayer(opponentId);
if (opponent != null && watcher.checkPlayer(opponentId, controller.getId())) {
stringBuilder.append(opponent.getName());
// Add a ", " between names, but exclude adding one at the end
if (opponentIdIterator.hasNext()) {
stringBuilder.append(", ");
} else {
stringBuilder.append('.');
}
}
}
return stringBuilder.toString();
}
@Override
public Hint copy() {
return instance;
}
}
enum WeatheredSentinelsCanAttackSomeoneCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
WeatheredSentinelsLastTurnAttackersWatcher watcher = game.getState().getWatcher(WeatheredSentinelsLastTurnAttackersWatcher.class);
if (controller == null || watcher == null) {
return false;
}
for (UUID opponentId : game.getOpponents(controller.getId())) {
Player opponent = game.getPlayer(opponentId);
if (opponent != null && watcher.checkPlayer(controller.getId(), opponentId)) {
return true;
}
}
return false;
}
}
class WeatheredSentinelsLastTurnAttackersWatcher extends Watcher {
private final Map<UUID, Set<UUID>> playerMap = new HashMap<>();
WeatheredSentinelsLastTurnAttackersWatcher() {
super(WatcherScope.GAME);
}
@Override
public void watch(GameEvent event, Game game) {
switch (event.getType()) {
case BEGINNING_PHASE_PRE:
playerMap.remove(game.getActivePlayerId());
return;
case ATTACKER_DECLARED:
UUID attacker = event.getPlayerId();
Set<UUID> defenders = playerMap.getOrDefault(attacker, new HashSet<>());
defenders.add(event.getTargetId());
playerMap.put(attacker, defenders);
}
}
/**
* Checks if on attackerId's last turn they attacked defenderId.
*
* @param attackerId The ID of the player to see if they attacked the given defender on the attacker's last turn
* @param defenderId The ID of the player to see if they were attacked by the attacker on the attacker's last turn
* @return Whether the attacker attacked the defender on the attacker's last turn
*/
boolean checkPlayer(UUID attackerId, UUID defenderId) {
if (attackerId == null || defenderId == null) {
return false;
}
Set<UUID> defendersLastTurn = playerMap.get(defenderId);
return defendersLastTurn != null && defendersLastTurn.contains(attackerId);
}
}

View file

@ -70,23 +70,24 @@ class WildMagicSorcererGainCascadeFirstSpellCastFromExileEffect extends Continuo
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
for (StackObject stackObject : game.getStack()) {
// only spells cast, so no copies of spells
if ((stackObject instanceof Spell)
&& !stackObject.isCopy()
&& stackObject.isControlledBy(source.getControllerId())) {
Spell spell = (Spell) stackObject;
WildMagicSorcererWatcher watcher = game.getState().getWatcher(WildMagicSorcererWatcher.class);
if (watcher != null
&& FirstSpellCastFromExileEachTurnCondition.instance.apply(game, source)) {
game.getState().addOtherAbility(spell.getCard(), cascadeAbility);
}
WildMagicSorcererWatcher watcher = game.getState().getWatcher(WildMagicSorcererWatcher.class);
if (controller == null || watcher == null) {
return false;
}
for (StackObject stackObject : game.getStack()) {
// only spells cast, so no copies of spells
if ((stackObject instanceof Spell)
&& !stackObject.isCopy()
&& stackObject.isControlledBy(source.getControllerId())) {
Spell spell = (Spell) stackObject;
if (FirstSpellCastFromExileEachTurnCondition.instance.apply(game, source)) {
game.getState().addOtherAbility(spell.getCard(), cascadeAbility);
}
}
return true;
}
return false;
return true;
}
}
@ -100,8 +101,7 @@ enum FirstSpellCastFromExileEachTurnCondition implements Condition {
}
WildMagicSorcererWatcher watcher = game.getState().getWatcher(WildMagicSorcererWatcher.class);
StackObject so = game.getStack().getFirst();
return so != null
&& watcher != null
return watcher != null
&& WildMagicSorcererWatcher.checkSpell(so, game);
}
}

View file

@ -32,7 +32,7 @@ public final class XandersPact extends CardImpl {
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{4}{B}{B}");
// Casualty 2
this.addAbility(new CasualtyAbility(this, 2));
this.addAbility(new CasualtyAbility(2));
// Each opponent exiles the top card of their library. You may cast spells from among those cards this turn. If you cast a spell this way, pay life equal to that spell's mana value rather than pay its mana cost.
this.getSpellAbility().addEffect(new XandersPactExileEffect());

View file

@ -20,7 +20,10 @@ public final class NewCapennaCommander extends ExpansionSet {
this.hasBasicLands = false;
cards.add(new SetCardInfo("Aether Snap", 241, Rarity.RARE, mage.cards.a.AetherSnap.class));
cards.add(new SetCardInfo("Aerial Extortionist", 11, Rarity.RARE, mage.cards.a.AerialExtortionist.class));
cards.add(new SetCardInfo("Agent's Toolkit", 66, Rarity.RARE, mage.cards.a.AgentsToolkit.class));
cards.add(new SetCardInfo("Agitator Ant", 263, Rarity.RARE, mage.cards.a.AgitatorAnt.class));
cards.add(new SetCardInfo("Anhelo, the Painter", 1, Rarity.MYTHIC, mage.cards.a.AnheloThePainter.class));
cards.add(new SetCardInfo("Ajani Unyielding", 324, Rarity.MYTHIC, mage.cards.a.AjaniUnyielding.class));
cards.add(new SetCardInfo("Alela, Artful Provocateur", 325, Rarity.MYTHIC, mage.cards.a.AlelaArtfulProvocateur.class));
cards.add(new SetCardInfo("Angelic Sleuth", 12, Rarity.RARE, mage.cards.a.AngelicSleuth.class));
@ -49,6 +52,7 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Bedevil", 331, Rarity.RARE, mage.cards.b.Bedevil.class));
cards.add(new SetCardInfo("Bellowing Mauler", 33, Rarity.RARE, mage.cards.b.BellowingMauler.class));
cards.add(new SetCardInfo("Bennie Bracks, Zoologist", 86, Rarity.MYTHIC, mage.cards.b.BennieBracksZoologist.class));
cards.add(new SetCardInfo("Bess, Soul Nourisher", 67, Rarity.RARE, mage.cards.b.BessSoulNourisher.class));
cards.add(new SetCardInfo("Blasphemous Act", 264, Rarity.RARE, mage.cards.b.BlasphemousAct.class));
cards.add(new SetCardInfo("Blighted Woodland", 388, Rarity.UNCOMMON, mage.cards.b.BlightedWoodland.class));
cards.add(new SetCardInfo("Bloodsoaked Champion", 243, Rarity.RARE, mage.cards.b.BloodsoakedChampion.class));
@ -85,6 +89,7 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Crash the Party", 57, Rarity.RARE, mage.cards.c.CrashTheParty.class));
cards.add(new SetCardInfo("Creeping Tar Pit", 396, Rarity.RARE, mage.cards.c.CreepingTarPit.class));
cards.add(new SetCardInfo("Crumbling Necropolis", 397, Rarity.UNCOMMON, mage.cards.c.CrumblingNecropolis.class));
cards.add(new SetCardInfo("Cryptic Pursuit", 70, Rarity.RARE, mage.cards.c.CrypticPursuit.class));
cards.add(new SetCardInfo("Crystalline Giant", 364, Rarity.RARE, mage.cards.c.CrystallineGiant.class));
cards.add(new SetCardInfo("Cultivate", 285, Rarity.UNCOMMON, mage.cards.c.Cultivate.class));
cards.add(new SetCardInfo("Currency Converter", 81, Rarity.RARE, mage.cards.c.CurrencyConverter.class));
@ -98,6 +103,7 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Deathreap Ritual", 336, Rarity.UNCOMMON, mage.cards.d.DeathreapRitual.class));
cards.add(new SetCardInfo("Declaration in Stone", 196, Rarity.RARE, mage.cards.d.DeclarationInStone.class));
cards.add(new SetCardInfo("Deep Analysis", 218, Rarity.COMMON, mage.cards.d.DeepAnalysis.class));
cards.add(new SetCardInfo("Denry Klin, Editor in Chief", 71, Rarity.RARE, mage.cards.d.DenryKlinEditorInChief.class));
cards.add(new SetCardInfo("Determined Iteration", 45, Rarity.RARE, mage.cards.d.DeterminedIteration.class));
cards.add(new SetCardInfo("Devoted Druid", 286, Rarity.UNCOMMON, mage.cards.d.DevotedDruid.class));
cards.add(new SetCardInfo("Dig Through Time", 219, Rarity.RARE, mage.cards.d.DigThroughTime.class));
@ -123,6 +129,7 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Fact or Fiction", 221, Rarity.UNCOMMON, mage.cards.f.FactOrFiction.class));
cards.add(new SetCardInfo("Fallen Shinobi", 338, Rarity.RARE, mage.cards.f.FallenShinobi.class));
cards.add(new SetCardInfo("False Floor", 82, Rarity.RARE, mage.cards.f.FalseFloor.class));
cards.add(new SetCardInfo("Family's Favor", 59, Rarity.RARE, mage.cards.f.FamilysFavor.class));
cards.add(new SetCardInfo("Farseek", 290, Rarity.COMMON, mage.cards.f.Farseek.class));
cards.add(new SetCardInfo("Fathom Mage", 339, Rarity.RARE, mage.cards.f.FathomMage.class));
cards.add(new SetCardInfo("Feed the Swarm", 250, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class));
@ -199,6 +206,7 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Magus of the Wheel", 271, Rarity.RARE, mage.cards.m.MagusOfTheWheel.class));
cards.add(new SetCardInfo("Make an Example", 37, Rarity.RARE, mage.cards.m.MakeAnExample.class));
cards.add(new SetCardInfo("March of the Multitudes", 346, Rarity.MYTHIC, mage.cards.m.MarchOfTheMultitudes.class));
cards.add(new SetCardInfo("Mari, the Killing Quill", 89, Rarity.RARE, mage.cards.m.MariTheKillingQuill.class));
cards.add(new SetCardInfo("Martial Coup", 206, Rarity.RARE, mage.cards.m.MartialCoup.class));
cards.add(new SetCardInfo("Mask of Riddles", 347, Rarity.UNCOMMON, mage.cards.m.MaskOfRiddles.class));
cards.add(new SetCardInfo("Mask of the Schemer", 28, Rarity.RARE, mage.cards.m.MaskOfTheSchemer.class));
@ -216,6 +224,7 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Nadir Kraken", 228, Rarity.RARE, mage.cards.n.NadirKraken.class));
cards.add(new SetCardInfo("Naya Panorama", 417, Rarity.COMMON, mage.cards.n.NayaPanorama.class));
cards.add(new SetCardInfo("Nesting Grounds", 418, Rarity.RARE, mage.cards.n.NestingGrounds.class));
cards.add(new SetCardInfo("Next of Kin", 62, Rarity.RARE, mage.cards.n.NextOfKin.class));
cards.add(new SetCardInfo("Nightmare Unmaking", 253, Rarity.RARE, mage.cards.n.NightmareUnmaking.class));
cards.add(new SetCardInfo("Noxious Gearhulk", 254, Rarity.MYTHIC, mage.cards.n.NoxiousGearhulk.class));
cards.add(new SetCardInfo("Oblivion Stone", 373, Rarity.RARE, mage.cards.o.OblivionStone.class));
@ -223,13 +232,16 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Oracle's Vault", 374, Rarity.RARE, mage.cards.o.OraclesVault.class));
cards.add(new SetCardInfo("Orzhov Advokist", 207, Rarity.UNCOMMON, mage.cards.o.OrzhovAdvokist.class));
cards.add(new SetCardInfo("Orzhov Signet", 375, Rarity.UNCOMMON, mage.cards.o.OrzhovSignet.class));
cards.add(new SetCardInfo("Oskar, Rubbish Reclaimer", 77, Rarity.RARE, mage.cards.o.OskarRubbishReclaimer.class));
cards.add(new SetCardInfo("Outpost Siege", 272, Rarity.RARE, mage.cards.o.OutpostSiege.class));
cards.add(new SetCardInfo("Overgrown Battlement", 303, Rarity.UNCOMMON, mage.cards.o.OvergrownBattlement.class));
cards.add(new SetCardInfo("Painful Truths", 255, Rarity.RARE, mage.cards.p.PainfulTruths.class));
cards.add(new SetCardInfo("Park Heights Maverick", 63, Rarity.RARE, mage.cards.p.ParkHeightsMaverick.class));
cards.add(new SetCardInfo("Parnesse, the Subtle Brush", 8, Rarity.MYTHIC, mage.cards.p.ParnesseTheSubtleBrush.class));
cards.add(new SetCardInfo("Path of Ancestry", 419, Rarity.COMMON, mage.cards.p.PathOfAncestry.class));
cards.add(new SetCardInfo("Path to Exile", 208, Rarity.UNCOMMON, mage.cards.p.PathToExile.class));
cards.add(new SetCardInfo("Perrie, the Pulverizer", 5, Rarity.MYTHIC, mage.cards.p.PerrieThePulverizer.class));
cards.add(new SetCardInfo("Phabine, Boss's Confidant", 9, Rarity.MYTHIC, mage.cards.p.PhabineBosssConfidant.class));
cards.add(new SetCardInfo("Planar Outburst", 209, Rarity.RARE, mage.cards.p.PlanarOutburst.class));
cards.add(new SetCardInfo("Ponder", 229, Rarity.COMMON, mage.cards.p.Ponder.class));
cards.add(new SetCardInfo("Port Town", 420, Rarity.RARE, mage.cards.p.PortTown.class));
@ -242,14 +254,17 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Protection Racket", 39, Rarity.RARE, mage.cards.p.ProtectionRacket.class));
cards.add(new SetCardInfo("Puppeteer Clique", 257, Rarity.RARE, mage.cards.p.PuppeteerClique.class));
cards.add(new SetCardInfo("Quietus Spike", 377, Rarity.RARE, mage.cards.q.QuietusSpike.class));
cards.add(new SetCardInfo("Rain of Riches", 50, Rarity.RARE, mage.cards.r.RainOfRiches.class));
cards.add(new SetCardInfo("Rakdos Signet", 378, Rarity.UNCOMMON, mage.cards.r.RakdosSignet.class));
cards.add(new SetCardInfo("Rampant Growth", 304, Rarity.COMMON, mage.cards.r.RampantGrowth.class));
cards.add(new SetCardInfo("Reign of the Pit", 258, Rarity.RARE, mage.cards.r.ReignOfThePit.class));
cards.add(new SetCardInfo("Rekindling Phoenix", 273, Rarity.MYTHIC, mage.cards.r.RekindlingPhoenix.class));
cards.add(new SetCardInfo("Resourceful Defense", 19, Rarity.RARE, mage.cards.r.ResourcefulDefense.class));
cards.add(new SetCardInfo("Rishkar's Expertise", 306, Rarity.RARE, mage.cards.r.RishkarsExpertise.class));
cards.add(new SetCardInfo("Rishkar, Peema Renegade", 305, Rarity.RARE, mage.cards.r.RishkarPeemaRenegade.class));
cards.add(new SetCardInfo("Rite of the Raging Storm", 274, Rarity.UNCOMMON, mage.cards.r.RiteOfTheRagingStorm.class));
cards.add(new SetCardInfo("River's Rebuke", 231, Rarity.RARE, mage.cards.r.RiversRebuke.class));
cards.add(new SetCardInfo("Riveteers Confluence", 79, Rarity.RARE, mage.cards.r.RiveteersConfluence.class));
cards.add(new SetCardInfo("Roalesk, Apex Hybrid", 349, Rarity.MYTHIC, mage.cards.r.RoaleskApexHybrid.class));
cards.add(new SetCardInfo("Rogue's Passage", 422, Rarity.UNCOMMON, mage.cards.r.RoguesPassage.class));
cards.add(new SetCardInfo("Rose Room Treasurer", 51, Rarity.RARE, mage.cards.r.RoseRoomTreasurer.class));
@ -267,15 +282,19 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Shadowblood Ridge", 426, Rarity.RARE, mage.cards.s.ShadowbloodRidge.class));
cards.add(new SetCardInfo("Shadowmage Infiltrator", 351, Rarity.UNCOMMON, mage.cards.s.ShadowmageInfiltrator.class));
cards.add(new SetCardInfo("Shamanic Revelation", 311, Rarity.RARE, mage.cards.s.ShamanicRevelation.class));
cards.add(new SetCardInfo("Shield Broker", 29, Rarity.RARE, mage.cards.s.ShieldBroker.class));
cards.add(new SetCardInfo("Silent-Blade Oni", 352, Rarity.RARE, mage.cards.s.SilentBladeOni.class));
cards.add(new SetCardInfo("Sinister Concierge", 30, Rarity.RARE, mage.cards.s.SinisterConcierge.class));
cards.add(new SetCardInfo("Skyboon Evangelist", 20, Rarity.RARE, mage.cards.s.SkyboonEvangelist.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Skyboon Evangelist", 121, Rarity.RARE, mage.cards.s.SkyboonEvangelist.class, NON_FULL_USE_VARIOUS));
cards.add(new SetCardInfo("Skyclave Shade", 260, Rarity.RARE, mage.cards.s.SkyclaveShade.class));
cards.add(new SetCardInfo("Skycloud Expanse", 427, Rarity.RARE, mage.cards.s.SkycloudExpanse.class));
cards.add(new SetCardInfo("Skyship Plunderer", 232, Rarity.UNCOMMON, mage.cards.s.SkyshipPlunderer.class));
cards.add(new SetCardInfo("Skyway Robber", 31, Rarity.RARE, mage.cards.s.SkywayRobber.class));
cards.add(new SetCardInfo("Slippery Bogbonder", 312, Rarity.RARE, mage.cards.s.SlipperyBogbonder.class));
cards.add(new SetCardInfo("Smoldering Marsh", 428, Rarity.RARE, mage.cards.s.SmolderingMarsh.class));
cards.add(new SetCardInfo("Smuggler's Share", 21, Rarity.RARE, mage.cards.s.SmugglersShare.class));
cards.add(new SetCardInfo("Smuggler's Buggy", 84, Rarity.RARE, mage.cards.s.SmugglersBuggy.class));
cards.add(new SetCardInfo("Sol Ring", 379, Rarity.UNCOMMON, mage.cards.s.SolRing.class));
cards.add(new SetCardInfo("Solemn Simulacrum", 380, Rarity.RARE, mage.cards.s.SolemnSimulacrum.class));
cards.add(new SetCardInfo("Spellbinding Soprano", 53, Rarity.RARE, mage.cards.s.SpellbindingSoprano.class));
@ -287,10 +306,12 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Stolen Identity", 233, Rarity.RARE, mage.cards.s.StolenIdentity.class));
cards.add(new SetCardInfo("Storm of Forms", 32, Rarity.RARE, mage.cards.s.StormOfForms.class));
cards.add(new SetCardInfo("Strionic Resonator", 381, Rarity.RARE, mage.cards.s.StrionicResonator.class));
cards.add(new SetCardInfo("Syrix, Carrier of the Flame", 80, Rarity.RARE, mage.cards.s.SyrixCarrierOfTheFlame.class));
cards.add(new SetCardInfo("Sun Titan", 210, Rarity.MYTHIC, mage.cards.s.SunTitan.class));
cards.add(new SetCardInfo("Sungrass Prairie", 430, Rarity.RARE, mage.cards.s.SungrassPrairie.class));
cards.add(new SetCardInfo("Sunken Hollow", 431, Rarity.RARE, mage.cards.s.SunkenHollow.class));
cards.add(new SetCardInfo("Swiftfoot Boots", 382, Rarity.UNCOMMON, mage.cards.s.SwiftfootBoots.class));
cards.add(new SetCardInfo("Swindler's Scheme", 88, Rarity.RARE, mage.cards.s.SwindlersScheme.class));
cards.add(new SetCardInfo("Swords to Plowshares", 211, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class));
cards.add(new SetCardInfo("Sylvan Offering", 314, Rarity.RARE, mage.cards.s.SylvanOffering.class));
cards.add(new SetCardInfo("Talrand's Invocation", 234, Rarity.UNCOMMON, mage.cards.t.TalrandsInvocation.class));
@ -301,11 +322,13 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Temple of Triumph", 437, Rarity.RARE, mage.cards.t.TempleOfTriumph.class));
cards.add(new SetCardInfo("Temple of the False God", 436, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class));
cards.add(new SetCardInfo("Temur Sabertooth", 315, Rarity.UNCOMMON, mage.cards.t.TemurSabertooth.class));
cards.add(new SetCardInfo("Tenuous Truce", 87, Rarity.RARE, mage.cards.t.TenuousTruce.class));
cards.add(new SetCardInfo("Terminate", 353, Rarity.UNCOMMON, mage.cards.t.Terminate.class));
cards.add(new SetCardInfo("Tezzeret's Gambit", 235, Rarity.RARE, mage.cards.t.TezzeretsGambit.class));
cards.add(new SetCardInfo("The Beamtown Bullies", 6, Rarity.MYTHIC, mage.cards.t.TheBeamtownBullies.class));
cards.add(new SetCardInfo("Thief of Sanity", 354, Rarity.RARE, mage.cards.t.ThiefOfSanity.class));
cards.add(new SetCardInfo("Thragtusk", 316, Rarity.RARE, mage.cards.t.Thragtusk.class));
cards.add(new SetCardInfo("Threefold Signal", 93, Rarity.MYTHIC, mage.cards.t.ThreefoldSignal.class));
cards.add(new SetCardInfo("Thriving Bluff", 438, Rarity.COMMON, mage.cards.t.ThrivingBluff.class));
cards.add(new SetCardInfo("Thriving Grove", 439, Rarity.COMMON, mage.cards.t.ThrivingGrove.class));
cards.add(new SetCardInfo("Thriving Heath", 440, Rarity.COMMON, mage.cards.t.ThrivingHeath.class));
@ -322,6 +345,7 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Twinning Staff", 383, Rarity.RARE, mage.cards.t.TwinningStaff.class));
cards.add(new SetCardInfo("Urban Evolution", 355, Rarity.UNCOMMON, mage.cards.u.UrbanEvolution.class));
cards.add(new SetCardInfo("Utter End", 356, Rarity.RARE, mage.cards.u.UtterEnd.class));
cards.add(new SetCardInfo("Vazi, Keen Negotiator", 92, Rarity.RARE, mage.cards.v.VaziKeenNegotiator.class));
cards.add(new SetCardInfo("Victimize", 261, Rarity.UNCOMMON, mage.cards.v.Victimize.class));
cards.add(new SetCardInfo("Vivid Creek", 444, Rarity.UNCOMMON, mage.cards.v.VividCreek.class));
cards.add(new SetCardInfo("Vivid Grove", 445, Rarity.UNCOMMON, mage.cards.v.VividGrove.class));
@ -331,7 +355,9 @@ public final class NewCapennaCommander extends ExpansionSet {
cards.add(new SetCardInfo("Wall of Roots", 319, Rarity.COMMON, mage.cards.w.WallOfRoots.class));
cards.add(new SetCardInfo("Warstorm Surge", 277, Rarity.RARE, mage.cards.w.WarstormSurge.class));
cards.add(new SetCardInfo("Waste Management", 40, Rarity.RARE, mage.cards.w.WasteManagement.class));
cards.add(new SetCardInfo("Wave of Rats", 41, Rarity.RARE, mage.cards.w.WaveOfRats.class));
cards.add(new SetCardInfo("Wayfarer's Bauble", 384, Rarity.COMMON, mage.cards.w.WayfarersBauble.class));
cards.add(new SetCardInfo("Weathered Sentinels", 85, Rarity.RARE, mage.cards.w.WeatheredSentinels.class));
cards.add(new SetCardInfo("Whirler Rogue", 238, Rarity.UNCOMMON, mage.cards.w.WhirlerRogue.class));
cards.add(new SetCardInfo("Wickerbough Elder", 320, Rarity.COMMON, mage.cards.w.WickerboughElder.class));
cards.add(new SetCardInfo("Windbrisk Heights", 447, Rarity.RARE, mage.cards.w.WindbriskHeights.class));

View file

@ -0,0 +1,110 @@
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;
/**
* @author Alex-Vasile
*/
public class CasualtyTest extends CardTestPlayerBase {
// Instant
// {1}{U}
// Casualty 1
// Look at the top two cards of your library. Put one of them into your hand and the other on the bottom of your library.
private static final String aLittleChat = "A Little Chat";
// Planeswalker
// {1}{B}{R}
// Casualty X
// The copy isnt legendary and has starting loyalty X.
// 7: Target player draws seven cards and loses 7 life.
private static final String obNixilisTheAdversary = "Ob Nixilis, the Adversary";
// 7/7 used as casualty
private static final String aetherwindBasker = "Aetherwind Basker";
/**
* Test Casualty on sorcery/instant.
*/
@Test
public void testCasualtySorceryInstant() {
addCard(Zone.BATTLEFIELD, playerA, aetherwindBasker);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.HAND, playerA, aLittleChat);
addCard(Zone.LIBRARY, playerA, "Desert", 4);
setStrictChooseMode(true);
skipInitShuffling();
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aLittleChat);
setChoice(playerA, "Yes");
setChoice(playerA, aetherwindBasker);
addTarget(playerA, "Desert");
addTarget(playerA, "Desert");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertHandCount(playerA, "Desert", 2);
assertGraveyardCount(playerA, aetherwindBasker, 1);
}
/**
* Test that casualty will only let you pay it once.
*/
@Test
public void testCanOnlyPayCasualtyOnce() {
addCard(Zone.BATTLEFIELD, playerA, aetherwindBasker, 2);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.HAND, playerA, aLittleChat);
addCard(Zone.LIBRARY, playerA, "Desert", 4);
setStrictChooseMode(true);
skipInitShuffling();
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aLittleChat);
setChoice(playerA, "Yes");
setChoice(playerA, aetherwindBasker);
// If a second target was possible, it would have prompted us for another and this test would fail when strict choose mode was on
addTarget(playerA, "Desert");
addTarget(playerA, "Desert");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertHandCount(playerA, "Desert", 2);
assertGraveyardCount(playerA, aetherwindBasker, 1);
assertPermanentCount(playerA, aetherwindBasker, 1);
}
/**
* Test Casualty on a creature.
* Test variable casualty.
*/
@Test
public void testVariableCasualtyOnCreature() {
addCard(Zone.BATTLEFIELD, playerA, aetherwindBasker);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.HAND, playerA, obNixilisTheAdversary);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, obNixilisTheAdversary);
setChoice(playerA, aetherwindBasker);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertGraveyardCount(playerA, aetherwindBasker, 1);
assertPermanentCount(playerA, obNixilisTheAdversary, 2); // 2 were created, but the token died when using its -7 ability
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-7:"); // -7 life and draw 7 cards
addTarget(playerA, playerA);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, obNixilisTheAdversary, 1);
assertLife(playerA, 20 - 7);
assertHandCount(playerA, 7);
}
}

View file

@ -0,0 +1,192 @@
package org.mage.test.cards.abilities.oneshot;
import mage.abilities.keyword.HasteAbility;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* @author Alex-Vasile
*/
public class PutCardFromOneOfTwoZonesOntoBattlefieldEffectTest extends CardTestPlayerBase {
// 5: You may put a creature card with mana value less than or equal to the number of lands you control onto the battlefield from your hand or graveyard with two +1/+1 counters on it.
private static final String nissa = "Nissa of Shadowed Boughs";
// {4}{B}{R}
// When Swift Warkite enters the battlefield, you may put a creature card with mana value 3 or less from your hand or graveyard onto the battlefield.
// That creature gains haste.
// Return it to your hand at the beginning of the next end step.
private static final String swift = "Swift Warkite";
// Simple 1/1 for Swift Warkite to put on the battlefield with its ETB
private static final String sliver = "Metallic Sliver";
// 2: You may put an Equipment card from your hand or graveyard onto the battlefield.
private static final String nahiri = "Nahiri, the Lithomancer";
// Equipment cards for Nahiri
private static final String vorpal = "Vorpal Sword";
private static final String axe = "Bloodforged Battle-Axe";
/**
* Test with no matching cards in hand or graveyard.
*/
@Test
public void testNoMatches() {
addCard(Zone.BATTLEFIELD, playerA, nahiri);
setStrictChooseMode(true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2");
// The player should not be prompted for a choice
setStopAt(1, PhaseStep.END_TURN);
execute();
}
/**
* Test with matching cards only in graveyard.
*/
@Test
public void testOnlyGraveyardHasMatches() {
addCard(Zone.BATTLEFIELD, playerA, nahiri);
addCard(Zone.GRAVEYARD, playerA, vorpal);
setStrictChooseMode(true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2");
setChoice(playerA, vorpal);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, vorpal, 1);
}
/**
* Test with matching cards only in hand.
*/
@Test
public void testOnlyHandHasMatches() {
addCard(Zone.BATTLEFIELD, playerA, nahiri);
addCard(Zone.HAND, playerA, vorpal);
setStrictChooseMode(true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2");
setChoice(playerA, vorpal);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, vorpal, 1);
}
/**
* Test with matching cards in both hand and graveyard.
*/
@Test
public void testBothHandAndGraveyardHaveMatches() {
addCard(Zone.BATTLEFIELD, playerA, nahiri);
addCard(Zone.HAND, playerA, vorpal);
addCard(Zone.GRAVEYARD, playerA, axe);
setStrictChooseMode(true);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2");
setChoice(playerA, "Yes"); // For player this choice is "Hand" but tests require "Yes"
setChoice(playerA, vorpal);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, vorpal, 1);
}
/**
* Test {@link mage.cards.n.NissaOfShadowedBoughs Nissa of Shadowed Boughs}
* Starting loyalty = 4
* You may put a creature card with mana value less than or equal to the number of lands you control
* onto the battlefield from your hand or graveyard
* with two +1/+1 counters on it.
*/
@Test
public void testNissaCanPlay() {
addCard(Zone.BATTLEFIELD, playerA, nissa);
addCard(Zone.HAND, playerA, swift); // {4}{B}{R}
addCard(Zone.HAND, playerA, "Mountain", 5);
addCard(Zone.HAND, playerA, "Mountain");
setStrictChooseMode(true);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-5");
setChoice(playerA, swift);
setChoice(playerA, "Yes"); // Say yes to Swift Warkite's ETB (no further choice needed since there are no possible options
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, nissa, 1);
assertPermanentCount(playerA, swift, 1);
assertCounterCount(swift, CounterType.P1P1, 2);
}
/**
* Test {@link mage.cards.n.NissaOfShadowedBoughs Nissa of Shadowed Boughs}
* Starting loyalty = 4
* You may put a creature card with mana value less than or equal to the number of lands you control
* onto the battlefield from your hand or graveyard
* with two +1/+1 counters on it.
*/
@Test
public void testNissaCantPlay() {
addCard(Zone.BATTLEFIELD, playerA, nissa);
addCard(Zone.HAND, playerA, swift); // {4}{B}{R}
addCard(Zone.HAND, playerA, "Mountain");
setStrictChooseMode(true);
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mountain");
activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "-5");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertGraveyardCount(playerA, nissa, 1);
}
/**
* Test {@link mage.cards.s.SwiftWarkite Swift Warkite}
* When Swift Warkite enters the battlefield, you may put a creature card with converted mana cost 3 or less from your hand or graveyard onto the battlefield.
* That creature gains haste.
* Return it to your hand at the beginning of the next end step.
*/
@Test
public void testSwiftWarkite() {
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.HAND, playerA, swift);
addCard(Zone.HAND, playerA, sliver);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, swift);
setChoice(playerA, "Yes"); // Yes to activating Swift Warkite's ETB
setChoice(playerA, sliver); // Pick the sliver for the ETB
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, sliver, 1);
assertAbility(playerA, sliver, HasteAbility.getInstance(), true);
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertHandCount(playerA, sliver, 1);
}
}

View file

@ -0,0 +1,79 @@
package org.mage.test.cards.single.gpt;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* {@link mage.cards.d.DjinnIlluminatus Djinn Illuminatus}
* <p>
* Each instant and sorcery spell you cast has replicate.
* The replicate cost is equal to its mana cost.
* (When you cast it, copy it for each time you paid its replicate cost. You may choose new targets for the copies.)
*
* @author Alex-Vasile
*/
public class DjinnIlluminatusTest extends CardTestPlayerBase {
private static final String djinnIlluminatus = "Djinn Illuminatus";
private static final String lightningBolt = "Lightning Bolt";
private static final String mountain = "Mountain";
/**
* Test that it works for you spells on your turn.
*/
@Test
public void testYourSpellYourTurn() {
addCard(Zone.BATTLEFIELD, playerA, djinnIlluminatus);
addCard(Zone.BATTLEFIELD, playerA, mountain, 2);
addCard(Zone.HAND, playerA, lightningBolt);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, playerB);
setChoice(playerA, "Yes"); // Replicate
setChoice(playerA, "No"); // Only replicate once
setChoice(playerA, "No"); // Don't change the target
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertLife(playerB, 14);
}
/**
* Test that it works for your spell on other's turn.
*/
@Test
public void testYourSpellNotYourTurn() {
addCard(Zone.BATTLEFIELD, playerA, djinnIlluminatus);
addCard(Zone.BATTLEFIELD, playerA, mountain, 2);
addCard(Zone.HAND, playerA, lightningBolt);
setStrictChooseMode(true);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, playerB);
setChoice(playerA, "Yes"); // Replicate
setChoice(playerA, "No"); // Only replicate once
setChoice(playerA, "No"); // Don't change the target
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertLife(playerB, 14);
}
/**
* Test that it does not copy other's spell.
*/
@Test
public void testOthersSpell() {
addCard(Zone.BATTLEFIELD, playerA, djinnIlluminatus);
addCard(Zone.BATTLEFIELD, playerB, mountain, 2);
addCard(Zone.HAND, playerB, lightningBolt);
setStrictChooseMode(true);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, lightningBolt, playerA);
setStopAt(2, PhaseStep.PRECOMBAT_MAIN);
execute();
assertLife(playerA, 17);
}
}

View file

@ -0,0 +1,198 @@
package org.mage.test.cards.single.ncc;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
import java.util.UUID;
/**
* {@link mage.cards.a.AnheloThePainter Anhelo, the Painter}
* The first instant or sorcery spell you cast each turn has casualty 2.
* (As you cast that spell, you may sacrifice a creature with power 2 or greater.
* When you do, copy the spell and you may choose new targets for the copy.)
*
* @author Alex-Vasile
*/
public class AnheloTest extends CardTestPlayerBase {
private static final String anhelo = "Anhelo, the Painter"; // {U}{B}{R}
private static final String lightningBolt = "Lightning Bolt"; // {R}
private static final String solRing = "Sol Ring"; // {1}
private static final String mountain = "Mountain";
// MDFC CreatureInstant
private static final String flamescrollCelebrant = "Flamescroll Celebrant"; // {1}{R}
private static final String revelInSilence = "Revel in Silence"; // {W}{W}
// 7/7 used as casualty
private static final String aetherwindBasker = "Aetherwind Basker";
// Instant
// {1}{U}
// Casualty 1
// Look at the top two cards of your library. Put one of them into your hand and the other on the bottom of your library.
private static final String aLittleChat = "A Little Chat";
/**
* Test that it works for sorcery, but only the first one.
*/
@Test
public void testWorksForFirstOnly() {
addCard(Zone.BATTLEFIELD, playerA, anhelo);
addCard(Zone.BATTLEFIELD, playerA, mountain, 2);
addCard(Zone.BATTLEFIELD, playerA, aetherwindBasker, 2);
addCard(Zone.HAND, playerA, lightningBolt, 2);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, playerB);
setChoice(playerA, "Yes"); // Cast with Casualty
setChoice(playerA, aetherwindBasker);
setChoice(playerA, "No"); // Don't change targets
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, playerB);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 2*3 - 3);
assertPermanentCount(playerA, aetherwindBasker, 1);
}
/**
* Test that it does not trigger for non-sorcery/instant.
*/
@Test
public void testNonSorceryOrInstant() {
addCard(Zone.BATTLEFIELD, playerA, anhelo);
addCard(Zone.BATTLEFIELD, playerA, mountain);
addCard(Zone.BATTLEFIELD, playerA, aetherwindBasker);
addCard(Zone.HAND, playerA, solRing);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, solRing);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, solRing, 1);
assertPermanentCount(playerA, aetherwindBasker, 1);
}
/**
* Test that the instant side of an MDFC gains casualty
*/
@Test
public void testInstantSideMDFC() {
addCard(Zone.BATTLEFIELD, playerA, anhelo);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, aetherwindBasker);
addCard(Zone.HAND, playerA, flamescrollCelebrant);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, revelInSilence);
setChoice(playerA, "Yes"); // Cast with Casualty
setChoice(playerA, aetherwindBasker);
// Spell has no targets, so not prompted to change them
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, aetherwindBasker, 0);
}
/**
* Test that the non-instant side of an MDFC (one which has an instant on the other side) does NOT gain casualty.
*/
@Test
public void testNonInstantSideMDFC() {
addCard(Zone.BATTLEFIELD, playerA, anhelo);
addCard(Zone.BATTLEFIELD, playerA, mountain, 2);
addCard(Zone.BATTLEFIELD, playerA, aetherwindBasker);
addCard(Zone.HAND, playerA, flamescrollCelebrant);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, flamescrollCelebrant);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, aetherwindBasker, 1);
assertPermanentCount(playerA, flamescrollCelebrant, 1);
}
/**
* Test that it works for one you cast on someone else's turn.
*/
@Test
public void testOnNotOwnTurn() {
addCard(Zone.BATTLEFIELD, playerA, anhelo);
addCard(Zone.BATTLEFIELD, playerA, mountain, 2);
addCard(Zone.BATTLEFIELD, playerA, aetherwindBasker, 2);
addCard(Zone.HAND, playerA, lightningBolt, 2);
setStrictChooseMode(true);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, playerB);
setChoice(playerA, "Yes"); // Cast with Casualty
setChoice(playerA, aetherwindBasker);
setChoice(playerA, "No"); // Don't change targets
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, playerB);
setStopAt(2, PhaseStep.END_TURN);
execute();
assertLife(playerB, 20 - 2*3 - 3);
assertPermanentCount(playerA, aetherwindBasker, 1);
}
/**
* Test that a card which already has Casualty will gain a second instance of Casualty and thus let you sacrifice twice in order to get 2 copies.
*/
@Test
public void testGainsSecondCasualty() {
addCard(Zone.BATTLEFIELD, playerA, anhelo);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerA, aetherwindBasker, 2);
addCard(Zone.HAND, playerA, aLittleChat);
addCard(Zone.LIBRARY, playerA, "Desert", 6);
setStrictChooseMode(true);
skipInitShuffling();
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, aLittleChat);
setChoice(playerA, "Yes"); // First instance of casualty
setChoice(playerA, "Yes"); // Second instance of casualty
setChoice(playerA, aetherwindBasker);
setChoice(playerA, aetherwindBasker);
setChoice(playerA, "When you do"); // Chose which of the two copies to put on the stack first
addTarget(playerA, "Desert");
addTarget(playerA, "Desert");
addTarget(playerA, "Desert");
setStopAt(2, PhaseStep.END_TURN);
execute();
assertHandCount(playerA, 3);
assertPermanentCount(playerA, aetherwindBasker, 0);
}
/**
* Test that it does not work for opponents on your turn or on their.
*/
@Test
public void testOpponentCasts() {
addCard(Zone.BATTLEFIELD, playerA, anhelo);
addCard(Zone.BATTLEFIELD, playerB, mountain, 2);
addCard(Zone.BATTLEFIELD, playerB, aetherwindBasker, 2);
addCard(Zone.HAND, playerB, lightningBolt, 2);
setStrictChooseMode(true);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, lightningBolt, playerA);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, lightningBolt, playerA);
setStopAt(2, PhaseStep.END_TURN);
execute();
assertLife(playerA, 20 - 3 - 3);
assertPermanentCount(playerB, aetherwindBasker, 2);
}
}

View file

@ -0,0 +1,83 @@
package org.mage.test.cards.single.ncc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* {@link mage.cards.b.BessSoulNourisher Bess, Soul Nourisher}
* <p>
* Whenever one or more other creatures with base power and toughness 1/1 enter the battlefield under your control,
* put a +1/+1 counter on Bess, Soul Nourisher.
* <p>
* Whenever Bess attacks, each other creature you control with base power and toughness 1/1 gets +X/+X until end of turn,
* where X is the number of +1/+1 counters on Bess.
*
* @author Alex-Vasile
*/
public class BessSoulNourisherTest extends CardTestPlayerBase {
// {1}{G}{W}
private static final String bessSoulNourisher = "Bess, Soul Nourisher";
// {3}{W}
// Create three 1/1 white Soldier creature tokens.
private static final String captainsCall = "Captain's Call";
/**
* Test that it only triggers once for a group entering
*/
@Test
public void testEntersGroup() {
addCard(Zone.BATTLEFIELD, playerA, bessSoulNourisher);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
addCard(Zone.HAND, playerA, captainsCall);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, captainsCall);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Soldier Token", 3);
assertCounterCount(playerA, bessSoulNourisher, CounterType.P1P1, 1); // Should only get one +1/+1 since all soldiers tokens enter at once
}
/**
* Test that the boosting corectly affects only creatures with BASE power and toughness of 1/1.
* Vodalian is a 1/1 so it should be buffed
* Artic merfolk is a 1/1 buffed to a 2/2, but it still has BASE PT of 1/1, so it should be buffed.
* Banewhip Punisher base 2/2 with a -1/-1 counter on it, so a 1/1, but its BASE is still 2/2, so it should not be buffed.
*/
@Test
public void testBoost() {
// 1/1
// Other Merfolk you control get +1/+1.
String vodalianHexcatcher = "Vodalian Hexcatcher";
// 1/1
String arcticMerfolk = "Arctic Merfolk";
// 2/2
// When Banewhip Punisher enters the battlefield, you may put a -1/-1 counter on target creature.
String banewhipPunisher = "Banewhip Punisher";
addCard(Zone.BATTLEFIELD, playerA, "Underground Sea", 5);
addCard(Zone.BATTLEFIELD, playerA, bessSoulNourisher);
addCard(Zone.BATTLEFIELD, playerA, arcticMerfolk);
addCard(Zone.HAND, playerA, banewhipPunisher);
addCard(Zone.HAND, playerA, vodalianHexcatcher);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, banewhipPunisher, true);
setChoice(playerA, "Yes");
addTarget(playerA, banewhipPunisher);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, vodalianHexcatcher);
attack(1, playerA, bessSoulNourisher);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertCounterCount(bessSoulNourisher, CounterType.P1P1, 1);
assertLife(playerB, 20 - 2);
assertPowerToughness(playerA, vodalianHexcatcher, 2, 2); // 1/1 + 1/1 from Bess
assertPowerToughness(playerA, arcticMerfolk, 3, 3); // 1/1 + (1/1 from Bess) + (1/1 Vodalian)
}
}

View file

@ -0,0 +1,82 @@
package org.mage.test.cards.single.ncc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* {@link mage.cards.m.MariTheKillingQuill Mari, the Killing Quill} {1}{B}{B}
* <p>
* Whenever a creature an opponent controls dies, exile it with a hit counter on it.
* <p>
* Assassins, Mercenaries, and Rogues you control have deathtouch and
* "Whenever this creature deals combat damage to a player, you may remove a hit counter from a card that player owns in exile.
* If you do, draw a card and create two Treasure tokens."
*
* @author Alex-Vasile
*/
public class MariTheKillingQuillTest extends CardTestPlayerBase {
private static final String mari = "Mari, the Killing Quill";
private static final String lightningBolt = "Lightning Bolt";
// Sliver with no ability 1/1
private static final String sliver = "Metallic Sliver";
// Changeling 1/1
private static final String automation = "Universal Automaton";
/**
* Test that an opponent's creature will get exiled with a hit counter.
* And that one of ours does not.
*/
@Test
public void testExiledWithCounter() {
addCard(Zone.HAND, playerA, lightningBolt, 2);
addCard(Zone.BATTLEFIELD, playerA, mari);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2);
addCard(Zone.BATTLEFIELD, playerA, automation);
addCard(Zone.BATTLEFIELD, playerB, sliver);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, sliver);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, automation);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertExileCount(playerA, 0);
assertGraveyardCount(playerA, automation, 1);
assertExileCount(playerB, sliver, 1);
assertCounterOnExiledCardCount(sliver, CounterType.HIT, 1);
}
/**
* Test that an opponent's creature will get exiled with a hit counter.
* And that one of ours does not.
*/
@Test
public void testDrawAndTreasure() {
addCard(Zone.HAND, playerA, lightningBolt);
addCard(Zone.BATTLEFIELD, playerA, mari);
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.BATTLEFIELD, playerA, automation);
addCard(Zone.BATTLEFIELD, playerB, sliver);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, sliver);
attack(1, playerA, automation, playerB);
setChoice(playerA, "Yes");
setChoice(playerA, sliver);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertHandCount(playerA, 1);
assertPermanentCount(playerA, "Treasure Token", 2);
assertExileCount(playerB, sliver, 1);
assertCounterOnExiledCardCount(sliver, CounterType.HIT, 0);
}
}

View file

@ -0,0 +1,159 @@
package org.mage.test.cards.single.ncc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* {@link mage.cards.r.ResourcefulDefense Resourceful Defense} {2}{W}
* <p>
* Whenever a permanent you control leaves the battlefield, if it had counters on it,
* put those counters on target permanent you control.
* <p>
* {4}{W}: Move any number of counters from target permanent you control to another target permanent you control.
*
* @author Alex-Vasile
*/
public class ResourcefulDefenseTest extends CardTestPlayerBase {
private static final String resourcefulDefense = "Resourceful Defense";
// Vivid Creek enters the battlefield tapped with two charge counters on it.
private static final String vividCreek = "Vivid Creek";
private static final String everflowingChalice = "Everflowing Chalice";
// Steelbane Hydra enters the battlefield with X +1/+1 counters on it.
private static final String steelbaneHydra = "Steelbane Hydra"; // {X}{G}{G}
private static final String lightningBolt = "Lightning Bolt";
/**
* Move counters from a creature that died.
*/
@Test
public void testMoveWhenDied() {
addCard(Zone.BATTLEFIELD, playerA, "Archway Commons", 9);
addCard(Zone.BATTLEFIELD, playerA, resourcefulDefense);
addCard(Zone.BATTLEFIELD, playerA, everflowingChalice);
addCard(Zone.HAND, playerA, steelbaneHydra);
addCard(Zone.HAND, playerA, lightningBolt);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, steelbaneHydra);
setChoice(playerA, "X=1");
castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, lightningBolt, steelbaneHydra);
addTarget(playerA, everflowingChalice);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertCounterCount(everflowingChalice, CounterType.P1P1, 1);
}
/**
* Move all of one counter from one permanent to another when the source only has one coutner type.
*/
@Test
public void testMoveAllSingleCounters() {
addCard(Zone.BATTLEFIELD, playerA, "Archway Commons", 5);
addCard(Zone.BATTLEFIELD, playerA, resourcefulDefense);
addCard(Zone.BATTLEFIELD, playerA, vividCreek);
addCard(Zone.BATTLEFIELD, playerA, everflowingChalice);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{W}: ");
addTarget(playerA, vividCreek);
addTarget(playerA, everflowingChalice);
setChoiceAmount(playerA, 2);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertCounterCount(vividCreek, CounterType.CHARGE, 0);
assertCounterCount(everflowingChalice, CounterType.CHARGE, 2);
}
/**
* Move some of one counter from one permanent to another when the source only has one coutner type.
*/
@Test
public void testSomeAllSingleCounters() {
addCard(Zone.BATTLEFIELD, playerA, "Archway Commons", 5);
addCard(Zone.BATTLEFIELD, playerA, resourcefulDefense);
addCard(Zone.BATTLEFIELD, playerA, vividCreek);
addCard(Zone.BATTLEFIELD, playerA, everflowingChalice);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{W}: ");
addTarget(playerA, vividCreek);
addTarget(playerA, everflowingChalice);
setChoiceAmount(playerA, 1);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertCounterCount(vividCreek, CounterType.CHARGE, 1);
assertCounterCount(everflowingChalice, CounterType.CHARGE, 1);
}
/**
* Move multiple counter types from one permanent to another.
*
* Also tests that when a creature without counters dies that you won't be prompted.
* The hydra has no counters after the second activation and will die because toughtness==0, but we aren't prompted
* for targets when it dies.
*/
@Test
public void testMoveAllMultipleCounters() {
addCard(Zone.BATTLEFIELD, playerA, "Archway Commons", 8);
addCard(Zone.BATTLEFIELD, playerA, resourcefulDefense);
addCard(Zone.BATTLEFIELD, playerA, vividCreek);
addCard(Zone.HAND, playerA, steelbaneHydra);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, steelbaneHydra);
setChoice(playerA, "X=1");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{W}: ");
addTarget(playerA, vividCreek);
addTarget(playerA, steelbaneHydra);
setChoiceAmount(playerA, 2);
waitStackResolved(3, PhaseStep.PRECOMBAT_MAIN);
activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{W}: ");
addTarget(playerA, steelbaneHydra);
addTarget(playerA, vividCreek);
setChoiceAmount(playerA, 2);
setChoiceAmount(playerA, 1);
setStopAt(3, PhaseStep.END_TURN);
execute();
assertCounterCount(vividCreek, CounterType.CHARGE, 2);
assertCounterCount(vividCreek, CounterType.P1P1, 1);
assertGraveyardCount(playerA, steelbaneHydra, 1);
}
/**
* Move multiple counter types from a creature that died.
*/
@Test
public void testMoveMultipleWhenDied() {
addCard(Zone.BATTLEFIELD, playerA, "Archway Commons", 9);
addCard(Zone.BATTLEFIELD, playerA, resourcefulDefense);
addCard(Zone.BATTLEFIELD, playerA, everflowingChalice);
addCard(Zone.BATTLEFIELD, playerA, vividCreek);
addCard(Zone.HAND, playerA, steelbaneHydra);
addCard(Zone.HAND, playerA, lightningBolt);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, steelbaneHydra);
setChoice(playerA, "X=1");
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{4}{W}: ");
addTarget(playerA, vividCreek);
addTarget(playerA, steelbaneHydra);
setChoiceAmount(playerA, 2);
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, steelbaneHydra);
addTarget(playerA, everflowingChalice);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertCounterCount(vividCreek, CounterType.CHARGE, 0);
assertCounterCount(everflowingChalice, CounterType.CHARGE, 2);
assertCounterCount(everflowingChalice, CounterType.P1P1, 1);
}
}

View file

@ -0,0 +1,73 @@
package org.mage.test.cards.single.ncc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestCommander4Players;
/**
* {@link mage.cards.s.ShieldBroker Shield Broker}
* {3}{U}{U}
* Creature Cephalid Advisor
* When Shield Broker enters the battlefield, put a shield counter on target noncommander creature you dont control.
* You gain control of that creature for as long as it has a shield counter on it.
* (If it would be dealt damage or destroyed, remove a shield counter from it instead.)
*/
public class ShieldBrokerTest extends CardTestCommander4Players {
private static final String shieldBroker = "Shield Broker";
private static final String rograkh = "Rograkh, Son of Rohgahh";
private static final String lightningBolt = "Lightning Bolt";
/**
* Test that it works for non-commander creature.
*/
@Test
public void testNonCommander() {
addCard(Zone.BATTLEFIELD, playerD, rograkh); // {0}
addCard(Zone.HAND, playerA, shieldBroker); // {3}{U}{U}
addCard(Zone.HAND, playerA, lightningBolt);
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, shieldBroker);
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
execute();
assertCounterCount(rograkh, CounterType.SHIELD, 1);
assertPermanentCount(playerA, rograkh, 1);
playLand(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Mountain");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, lightningBolt, rograkh);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertCounterCount(rograkh, CounterType.SHIELD, 0);
assertPermanentCount(playerA, rograkh, 0);
assertPermanentCount(playerD, rograkh, 1);
}
/**
* Test that it does not work for commander creature.
*/
@Test
public void testCommander() {
addCard(Zone.COMMAND, playerD, rograkh); // {0}
addCard(Zone.HAND, playerA, shieldBroker); // {3}{U}{U}
addCard(Zone.BATTLEFIELD, playerA, "Island", 5);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerD, rograkh);
castSpell(5, PhaseStep.PRECOMBAT_MAIN, playerA, shieldBroker);
setStopAt(5, PhaseStep.PRECOMBAT_MAIN);
execute();
assertCounterCount(rograkh, CounterType.SHIELD, 0);
assertPermanentCount(playerA, rograkh, 0);
assertPermanentCount(playerD, rograkh, 1);
}
}

View file

@ -0,0 +1,75 @@
package org.mage.test.cards.single.ncc;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.keyword.SuspendAbility;
import mage.constants.PhaseStep;
import mage.constants.TimingRule;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* {@link mage.cards.s.SinisterConcierge Sinister Concierge}
* 2/1
* When Sinister Concierge dies, you may exile it and put three time counters on it.
* If you do, exile up to one target creature and put three time counters on it.
* Each card exiled this way that doesnt have suspend gains suspend.
* @author Alex-Vasile
*/
public class SinisterConciergeTest extends CardTestPlayerBase {
private static final String sinisterConcierge = "Sinister Concierge"; // 2/1
private static final String bondedConstruct = "Bonded Construct"; // Simple 2/1
private static final String lightningBolt = "Lightning Bolt"; // {R}
/**
* Test that both cards are exiled properly.
*/
@Test
public void testWorking() {
addCard(Zone.HAND, playerA, lightningBolt);
addCard(Zone.BATTLEFIELD, playerA, sinisterConcierge);
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.BATTLEFIELD, playerB, bondedConstruct);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, sinisterConcierge);
setChoice(playerA, "Yes");
addTarget(playerA, bondedConstruct);
setStopAt(1, PhaseStep.END_COMBAT);
execute();
assertExileCount(playerA, sinisterConcierge, 1);
assertCounterOnExiledCardCount(sinisterConcierge, CounterType.TIME, 3);
assertExileCount(playerB, bondedConstruct, 1);
assertCounterOnExiledCardCount(bondedConstruct, CounterType.TIME, 3);
setStopAt(5, PhaseStep.PRECOMBAT_MAIN);
execute();
assertExileCount(playerA, sinisterConcierge, 1);
assertCounterOnExiledCardCount(sinisterConcierge, CounterType.TIME, 1);
assertExileCount(playerB, bondedConstruct, 1);
assertCounterOnExiledCardCount(bondedConstruct, CounterType.TIME, 1);
setStopAt(6, PhaseStep.PRECOMBAT_MAIN);
execute();
assertExileCount(playerA, sinisterConcierge, 1);
assertExileCount(playerB, bondedConstruct, 0);
assertPermanentCount(playerB, bondedConstruct, 1);
setStopAt(7, PhaseStep.PRECOMBAT_MAIN);
execute();
assertExileCount(playerA, sinisterConcierge, 0);
assertPermanentCount(playerA, sinisterConcierge, 1);
assertExileCount(playerB, bondedConstruct, 0);
assertPermanentCount(playerB, bondedConstruct, 1);
}
}

View file

@ -0,0 +1,63 @@
package org.mage.test.cards.single.ncc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* {@link mage.cards.s.SkywayRobber Skyway Robber}
* <p>
* Escape{3}{U}, Exile five other cards from your graveyard.
* (You may cast this card from your graveyard for its escape cost.)
* <p>
* Skyway Robber escapes with
* Whenever Skyway Robber deals combat damage to a player,
* you may cast an artifact, instant, or sorcery spell from among cards exiled with Skyway Robber
* without paying its mana cost.
* @author Alex-Vasile
*/
public class SkywayRobberTest extends CardTestPlayerBase {
private static final String skywayRobber = "Skyway Robber";
/**
* Check that you are not given the option to cast anything if there are no valid choices.
*/
@Test
public void testNoOption() {
addCard(Zone.GRAVEYARD, playerA, skywayRobber);
addCard(Zone.GRAVEYARD, playerA, "Mountain", 5);
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + skywayRobber + " with Escape");
attack(3, playerA, skywayRobber);
setStopAt(3, PhaseStep.END_COMBAT);
execute();
assertExileCount(playerA, "Mountain", 5);
assertLife(playerB, 17);
}
/**
* Check that the cast works.
*/
@Test
public void testCast() {
addCard(Zone.GRAVEYARD, playerA, skywayRobber);
addCard(Zone.GRAVEYARD, playerA, "Mountain", 4);
addCard(Zone.GRAVEYARD, playerA, "Sol Ring" );
addCard(Zone.BATTLEFIELD, playerA, "Island", 4);
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast " + skywayRobber + " with Escape");
attack(3, playerA, skywayRobber);
setStopAt(3, PhaseStep.END_TURN);
execute();
assertExileCount(playerA, "Mountain", 4);
assertPermanentCount(playerA, "Sol Ring", 1);
assertLife(playerB, 17);
}
}

View file

@ -0,0 +1,230 @@
package org.mage.test.cards.single.ncc;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import mage.counters.CounterType;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* {@link mage.cards.t.ThreefoldSignal Threefold Signal}
* <p>
* Each spell you cast thats exactly three colors has replicate {3}.
* @author Alex-Vasile
*/
public class ThreefoldSignalTest extends CardTestPlayerBase {
private static final String threefoldSignal = "Threefold Signal";
// R
private static final String lightningBolt = "Lightning Bolt";
// WUBRG
private static final String atogatog = "Atogatog";
// WUB
private static final String esperSojourners = "Esper Sojourners";
/**
* Check that it works for three-colored spells
*/
@Test
public void testShouldWork() {
addCard(Zone.BATTLEFIELD, playerA, threefoldSignal);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
addCard(Zone.HAND, playerA, esperSojourners);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, esperSojourners);
setChoice(playerA, true);
setChoice(playerA, false);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, esperSojourners, 2);
}
/**
* Check that it does not trigger for spells with less than three colors.
*/
@Test
public void testShouldNotWork1Color() {
addCard(Zone.BATTLEFIELD, playerA, threefoldSignal);
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.HAND, playerA, lightningBolt);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, lightningBolt, playerB);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertLife(playerB, 17);
}
/**
* Check that it does not trigger for spells with more than three colors.
*/
@Test
public void testShouldNotWork5Color() {
addCard(Zone.BATTLEFIELD, playerA, threefoldSignal);
addCard(Zone.BATTLEFIELD, playerA, "Plains");
addCard(Zone.BATTLEFIELD, playerA, "Island");
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
addCard(Zone.BATTLEFIELD, playerA, "Forest");
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
addCard(Zone.HAND, playerA, atogatog);
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, atogatog);
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, atogatog, 1);
}
/**
* Check that it does not trigger for spells opponents control.
*/
@Test
public void testShouldNotWorkOpponent() {
addCard(Zone.BATTLEFIELD, playerA, threefoldSignal);
addCard(Zone.BATTLEFIELD, playerB, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerB, "Island", 2);
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 2);
addCard(Zone.HAND, playerB, esperSojourners);
setStrictChooseMode(true);
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, esperSojourners);
setStopAt(2, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerB, esperSojourners, 1);
}
/**
* Check that casting one half of a split card doesn't trigger it even if the whole split card has 3 colors.
* Relevant ruling:
* 709.3a Only the chosen half is evaluated to see if it can be cast.
* Only that half is considered to be put onto the stack.
* 709.3b While on the stack, only the characteristics of the half being cast exist.
* The other halfs characteristics are treated as though they didnt exist.
*/
@Test
public void oneHalfOfSplitCardDoesntTrigger() {
// {G}{U} / {4}{W}{U}
// Beck: Whenever a creature enters the battlefield this turn, you may draw a card.
// Call: Create four 1/1 white Bird creature tokens with flying.
// Fuse
String beckCall = "Beck // Call";
addCard(Zone.HAND, playerA, beckCall);
addCard(Zone.BATTLEFIELD, playerA, threefoldSignal);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4 + 3); // For generic costs and to have enough for replicate
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Call");
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Bird Token", 4);
}
/**
* Test that casting a split card with fuse triggers if both halves together have 3 colors
*/
@Test
public void fusedSplitCardTriggers() {
// {G}{U} / {4}{W}{U}
// Beck: Whenever a creature enters the battlefield this turn, you may draw a card.
// Call: Create four 1/1 white Bird creature tokens with flying.
// Fuse
String beckCall = "Beck // Call";
addCard(Zone.HAND, playerA, beckCall);
addCard(Zone.BATTLEFIELD, playerA, threefoldSignal);
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 2);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 4 + 3); // For generic costs and to have enough for replicate
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Beck // Call");
setChoice(playerA, true); // Pay replicate once
setChoice(playerA, false); // Don't pay replicate twice
// Copy resolves, first Beck then call
setChoice(playerA, "Whenever", 3); // 4 triggers total, pick order for 3 and the 4th is auto-chosen
setChoice(playerA, true, 4); // Draw cards 4 times
// Original resolves
// There will be 8 ETB triggers. 4 creatures enter but there are 2 instances of Beck that were cast
setChoice(playerA, "Whenever", 7); // 8 triggers total, pick order for 7 and the 8th is auto-chosen
setChoice(playerA, true, 8); // Draw cards 8 times
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertPermanentCount(playerA, "Bird Token", 4 + 4);
assertHandCount(playerA, 4 + (4+4));
}
/**
* Test that casting a split card with fuse triggers if both halves together have 3 colors
*/
@Test
public void fusedSplitCardTriggers2() {
// {3}{B}{G} / {R}{G}
// Flesh: Exile target creature card from a graveyard.
// Put X +1/+1 counters on target creature, where X is the power of the card you exiled.
// Blood: Target creature you control deals damage equal to its power to any target.
// Fuse
String fleshBlood = "Flesh // Blood";
addCard(Zone.HAND, playerA, fleshBlood);
addCard(Zone.BATTLEFIELD, playerA, threefoldSignal);
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
addCard(Zone.BATTLEFIELD, playerA, "Forest", 2);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1+3 + 3); // For red + generic costs and to have enough for replicate
// All are 2/2
String lion = "Silvercoat Lion";
addCard(Zone.BATTLEFIELD, playerA, lion);
String griffin = "Abbey Griffin";
addCard(Zone.GRAVEYARD, playerA, griffin); // Exile with original cast
String centaur = "Accursed Centaur";
addCard(Zone.GRAVEYARD, playerA, centaur); // Exile with copy
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "fused Flesh // Blood");
setChoice(playerA, true); // Pay replicate once
setChoice(playerA, false); // Don't pay replicate twice
// Flesh
addTarget(playerA, griffin);
addTarget(playerA, lion);
// Blood
addTarget(playerA, lion);
addTarget(playerA, playerB);
// Copy of Flesh
setChoice(playerA, true); // Change the exile card from the Griffin
addTarget(playerA, centaur);
setChoice(playerA, false); // Don't change target from the lion
// Copy of Blood
setChoice(playerA, false); // Don't change target from lion
setChoice(playerA, false); // Don't change target from PlayerB
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertCounterCount(lion, CounterType.P1P1, 4); // 2 from the copy and two from the original cast
assertLife(playerB, 20 - (2+2) - (2+2+2));
assertExileCount(playerA, griffin, 1);
assertExileCount(playerA, centaur, 1);
}
}

View file

@ -0,0 +1,68 @@
package org.mage.test.cards.single.ncc;
import mage.abilities.keyword.IndestructibleAbility;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Assert;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
/**
* Weathered Sentinels
* {3}
* Artifact Creature Wall
* Defender, vigilance, reach, trample
* Weathered Sentinels can attack players who attacked you during their last turn as though it didnt have defender.
* Whenever Weathered Sentinels attacks, it gets +3/+3 and gains indestructible until end of turn.
*/
public class WeatheredSentinelsTest extends CardTestPlayerBase {
private static final String weatheredSentinels = "Weathered Sentinels";
// 1/1 Haste attacker
private static final String gingerBrute = "Gingerbrute";
/**
* Should not be able to attack a player who did not attack you on their last turn
*/
@Test
public void testCantAttackNonAttacker() {
addCard(Zone.BATTLEFIELD, playerA, weatheredSentinels);
attack(1, playerA, weatheredSentinels);
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
try {
execute();
} catch (Throwable e) {
if (!e.getMessage().contains("Player PlayerA must have 0 actions but found 1")) {
Assert.fail("Should have had error about playerA not being able to attack, but got:\n" + e.getMessage());
}
}
}
/**
* Should be able to attack a player that attacked you on their last turn, and it should get +3/+3 and indestructible until end of turn.
*/
@Test
public void testCanAttackAttacker() {
addCard(Zone.BATTLEFIELD, playerA, weatheredSentinels);
addCard(Zone.BATTLEFIELD, playerB, gingerBrute);
// Attack playerA
attack(2, playerB, gingerBrute);
// Attack back
attack(3, playerA, weatheredSentinels);
// Check that Weathered Sentinels has a +3/+3 and indestructible
setStopAt(3, PhaseStep.POSTCOMBAT_MAIN);
execute();
assertAbility(playerA, weatheredSentinels, IndestructibleAbility.getInstance(), true);
assertPowerToughness(playerA, weatheredSentinels, 5, 8);
// Check that Weathered Sentinels lost the abilities next turn
setStopAt(4, PhaseStep.PRECOMBAT_MAIN);
execute();
}
}

View file

@ -0,0 +1,95 @@
package org.mage.test.utils;
import mage.constants.PhaseStep;
import mage.constants.Zone;
import org.junit.Test;
import org.mage.test.serverside.base.CardTestPlayerBase;
public class CardUtilTest extends CardTestPlayerBase {
// Whenever you cast or copy an instant or sorcery spell, reveal the top card of your library.
// If its a nonland card, you may cast it by paying {1} rather than paying its mana cost.
// If its a land card, put it onto the battlefield.
private static final String jadzi = "Jadzi, Oracle of Arcavios";
// Whenever you discard a nonland card, you may cast it from your graveyard.
private static final String oskar = "Oskar, Rubbish Reclaimer";
// MDFC where the back side is a land "Akoum Teeth"
private static final String akoumWarrior = "Akoum Warrior"; // {5}{R}
// Discard your hand, then draw a card for each card youve discarded this turn.
private static final String changeOfFortune = "Change of Fortune"; // {3}{R}
// MDFC where both sides should be playable
private static final String birgi = "Birgi, God of Storytelling"; // {2}{R}, frontside of Harnfel
private static final String harnfel = "Harnfel, Horn of Bounty"; // {4}{R}, backside of Birgi
/**
* Test that it will for trigger for discarding a MDFC but will only let you cast the nonland side.
*/
@Test
public void cantPlayLandSideOfMDFC() {
addCard(Zone.HAND, playerA, changeOfFortune);
addCard(Zone.HAND, playerA, akoumWarrior);
addCard(Zone.BATTLEFIELD, playerA, oskar);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 10);
skipInitShuffling();
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, changeOfFortune);
setChoice(playerA, "Yes");
setStopAt(1, PhaseStep.DECLARE_ATTACKERS);
execute();
assertPermanentCount(playerA, akoumWarrior, 1);
}
/**
* Test that when both sides of a MDFC card match, we can choose either side.
*/
@Test
public void testFrontSideOfMDFC() {
addCard(Zone.HAND, playerA, changeOfFortune);
addCard(Zone.HAND, playerA, birgi, 2);
addCard(Zone.BATTLEFIELD, playerA, oskar);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 12);
skipInitShuffling();
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, changeOfFortune);
setChoice(playerA, "Whenever you discard");
setChoice(playerA, "Yes");
setChoice(playerA, "Cast " + birgi);
setChoice(playerA, "Yes");
setChoice(playerA, "Cast " + harnfel);
setStopAt(1, PhaseStep.DECLARE_ATTACKERS);
execute();
assertPermanentCount(playerA, birgi, 1);
assertPermanentCount(playerA, harnfel, 1);
}
/**
* Test that with Jadzi, you are able to play the nonland side of a MDFC, and that the alternative cost works properly.
*/
@Test
public void testJadziPlayingLandAndCast() {
addCard(Zone.BATTLEFIELD, playerA, jadzi);
addCard(Zone.BATTLEFIELD, playerA, "Mountain", 1+1+1);
addCard(Zone.HAND, playerA, "Lightning Bolt", 2);
addCard(Zone.LIBRARY, playerA, "Cragcrown Pathway");
addCard(Zone.LIBRARY, playerA, akoumWarrior);
skipInitShuffling();
setStrictChooseMode(true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
setChoice(playerA, "Yes");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", playerB);
setStopAt(1, PhaseStep.DECLARE_ATTACKERS);
execute();
assertPermanentCount(playerA, akoumWarrior, 1);
assertPermanentCount(playerA, "Cragcrown Pathway", 1);
}
}

View file

@ -8,6 +8,9 @@ import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.Set;
import java.util.UUID;
/**
* @author LevelX2
*/
@ -31,10 +34,22 @@ public class EnchantedPlayerAttackedTriggeredAbility extends TriggeredAbilityImp
public boolean checkTrigger(GameEvent event, Game game) {
Permanent enchantment = game.getPermanentOrLKIBattlefield(getSourceId());
Player controller = game.getPlayer(getControllerId());
if (controller != null && enchantment != null) {
return game.getCombat().getPlayerDefenders(game, false).contains(enchantment.getAttachedTo());
Player attacker = game.getPlayer(game.getCombat().getAttackingPlayerId());
if (controller == null || attacker == null || enchantment == null) {
return false;
}
return false;
Player enchantedPlayer = game.getPlayer(enchantment.getAttachedTo());
if (enchantedPlayer == null) {
return false;
}
Set<UUID> opponentIds = game.getOpponents(controller.getId());
if (!opponentIds.contains(attacker.getId()) || !opponentIds.contains(enchantedPlayer.getId())) {
return false;
}
return game.getCombat().getPlayerDefenders(game, false).contains(enchantment.getAttachedTo());
}
@Override

View file

@ -0,0 +1,95 @@
package mage.abilities.common;
import mage.MageItem;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.TargetController;
import mage.constants.Zone;
import mage.filter.FilterPermanent;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeGroupEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import java.util.Objects;
import java.util.stream.Stream;
/**
* "Whenever one or more {filter} enter the battlefield under {target controller} control,
*
* @author Alex-Vasile
*/
public class EntersBattlefieldOneOrMoreTriggeredAbility extends TriggeredAbilityImpl {
private final FilterPermanent filterPermanent;
private final TargetController targetController;
public EntersBattlefieldOneOrMoreTriggeredAbility(Effect effect, FilterPermanent filter, TargetController targetController) {
super(Zone.BATTLEFIELD, effect);
this.filterPermanent = filter;
this.targetController = targetController;
setTriggerPhrase(generateTriggerPhrase());
}
private EntersBattlefieldOneOrMoreTriggeredAbility(final EntersBattlefieldOneOrMoreTriggeredAbility ability) {
super(ability);
this.filterPermanent = ability.filterPermanent;
this.targetController = ability.targetController;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE_GROUP;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
ZoneChangeGroupEvent zEvent = (ZoneChangeGroupEvent) event;
Player controller = game.getPlayer(this.controllerId);
if (zEvent.getToZone() != Zone.BATTLEFIELD || controller == null) {
return false;
}
switch (this.targetController) {
case YOU:
if (!controller.getId().equals(zEvent.getPlayerId())) {
return false;
}
break;
case OPPONENT:
if (!controller.hasOpponent(zEvent.getPlayerId(), game)) {
return false;
}
break;
}
return Stream.concat(
zEvent.getTokens().stream(),
zEvent.getCards().stream()
.map(MageItem::getId)
.map(game::getPermanent)
.filter(Objects::nonNull)
).anyMatch(permanent -> filterPermanent.match(permanent, this.controllerId, this, game));
}
@Override
public EntersBattlefieldOneOrMoreTriggeredAbility copy() {
return new EntersBattlefieldOneOrMoreTriggeredAbility(this);
}
private String generateTriggerPhrase() {
StringBuilder sb = new StringBuilder("Whenever one or more " + this.filterPermanent.getMessage() + " enter the battlefield under ");
switch (targetController) {
case YOU:
sb.append("your control, ");
break;
case OPPONENT:
sb.append("an opponent's control, ");
break;
default:
throw new UnsupportedOperationException();
}
return sb.toString();
}
}

View file

@ -3,8 +3,11 @@ package mage.abilities.common;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.SpellAbility;
import mage.abilities.TriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.EntersBattlefieldEffect;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.GainAbilitySourceEffect;
import mage.abilities.keyword.EscapeAbility;
import mage.constants.AbilityType;
import mage.constants.Outcome;
@ -23,22 +26,22 @@ import java.util.UUID;
public class EscapesWithAbility extends EntersBattlefieldAbility {
private final int counters;
private final DelayedTriggeredAbility delayedTriggeredAbility;
private final TriggeredAbility triggeredAbility;
public EscapesWithAbility(int counters) {
this(counters, null);
}
public EscapesWithAbility(int counters, DelayedTriggeredAbility delayedTriggeredAbility) {
super(new EscapesWithEffect(counters, delayedTriggeredAbility), false);
public EscapesWithAbility(int counters, TriggeredAbility triggeredAbility) {
super(new EscapesWithEffect(counters, triggeredAbility), false);
this.counters = counters;
this.delayedTriggeredAbility = delayedTriggeredAbility;
this.triggeredAbility = triggeredAbility;
}
private EscapesWithAbility(final EscapesWithAbility ability) {
super(ability);
this.counters = ability.counters;
this.delayedTriggeredAbility = ability.delayedTriggeredAbility;
this.triggeredAbility = ability.triggeredAbility;
}
@Override
@ -48,27 +51,42 @@ public class EscapesWithAbility extends EntersBattlefieldAbility {
@Override
public String getRule() {
return "{this} escapes with " + CardUtil.numberToText(counters, "a")
+ " +1/+1 counter" + (counters > 1 ? 's' : "") + " on it."
+ (this.delayedTriggeredAbility != null ? " " + this.delayedTriggeredAbility.getRule() : "");
StringBuilder sb = new StringBuilder("{this} escapes with ");
if (counters > 0) {
sb.append(CardUtil.numberToText(counters, "a"));
sb.append(" +1/+1 counter");
sb.append((counters > 1 ? 's' : ""));
sb.append(" on it.");
}
if (triggeredAbility == null) {
// Do nothing
} else if (triggeredAbility instanceof DelayedTriggeredAbility) {
sb.append(this.triggeredAbility.getRule());
} else {
sb.append("\"");
sb.append(this.triggeredAbility.getRule());
sb.append("\"");
}
return sb.toString();
}
}
class EscapesWithEffect extends OneShotEffect {
private final int counter;
private final DelayedTriggeredAbility delayedTriggeredAbility;
private final TriggeredAbility triggeredAbility;
EscapesWithEffect(int counter, DelayedTriggeredAbility delayedTriggeredAbility) {
EscapesWithEffect(int counter, TriggeredAbility triggeredAbility) {
super(Outcome.BoostCreature);
this.counter = counter;
this.delayedTriggeredAbility = delayedTriggeredAbility;
this.triggeredAbility = triggeredAbility;
}
private EscapesWithEffect(final EscapesWithEffect effect) {
super(effect);
this.counter = effect.counter;
this.delayedTriggeredAbility = (effect.delayedTriggeredAbility == null ? null : effect.delayedTriggeredAbility.copy());
this.triggeredAbility = (effect.triggeredAbility == null ? null : effect.triggeredAbility.copy());
}
@Override
@ -80,16 +98,26 @@ class EscapesWithEffect extends OneShotEffect {
if (permanent == null) {
return false;
}
SpellAbility spellAbility = (SpellAbility) getValue(EntersBattlefieldEffect.SOURCE_CAST_SPELL_ABILITY);
if (!(spellAbility instanceof EscapeAbility)
|| !spellAbility.getSourceId().equals(source.getSourceId())
|| permanent.getZoneChangeCounter(game) != spellAbility.getSourceObjectZoneChangeCounter()) {
return false;
}
List<UUID> appliedEffects = (ArrayList<UUID>) this.getValue("appliedEffects");
permanent.addCounters(CounterType.P1P1.createInstance(counter), source.getControllerId(), source, game, appliedEffects);
if (this.delayedTriggeredAbility != null) {
game.addDelayedTriggeredAbility(this.delayedTriggeredAbility, source);
if (counter > 0) {
List<UUID> appliedEffects = (ArrayList<UUID>) this.getValue("appliedEffects");
permanent.addCounters(CounterType.P1P1.createInstance(counter), source.getControllerId(), source, game, appliedEffects);
}
if (this.triggeredAbility != null) {
if (triggeredAbility instanceof DelayedTriggeredAbility) {
game.addDelayedTriggeredAbility((DelayedTriggeredAbility) this.triggeredAbility, source);
} else {
ContinuousEffect gainsAbilityEffect = new GainAbilitySourceEffect(triggeredAbility);
game.addEffect(gainsAbilityEffect, source);
}
}
return true;
}

View file

@ -20,8 +20,10 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
// The source SPELL that triggered the ability will be set as target to effect
protected boolean rememberSource;
// Use it if you want remember CARD instead spell
// Use it if you want to remember CARD instead spell
protected boolean rememberSourceAsCard;
// Trigger only for spells cast from this zone. Default is from any zone.
private Zone fromZone = Zone.ALL;
public SpellCastControllerTriggeredAbility(Effect effect, boolean optional) {
this(Zone.BATTLEFIELD, effect, StaticFilters.FILTER_SPELL_A, optional, false);
@ -31,6 +33,11 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
this(effect, filter, optional, false);
}
public SpellCastControllerTriggeredAbility(Effect effect, FilterSpell filter, boolean optional, Zone fromZone) {
this(effect, filter, optional, false);
this.fromZone = fromZone;
}
public SpellCastControllerTriggeredAbility(Effect effect, FilterSpell filter, boolean optional, String rule) {
this(effect, filter, optional, false);
this.rule = rule;
@ -49,7 +56,7 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
this.filter = filter;
this.rememberSource = rememberSource;
this.rememberSourceAsCard = rememberSourceAsCard;
setTriggerPhrase("Whenever you cast " + filter.getMessage() + ", ");
setTriggerPhrase("Whenever you cast " + filter.getMessage() + (fromZone != Zone.ALL ? "from your " + fromZone.toString().toLowerCase() : "") + ", ");
}
public SpellCastControllerTriggeredAbility(final SpellCastControllerTriggeredAbility ability) {
@ -58,6 +65,7 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
this.rule = ability.rule;
this.rememberSource = ability.rememberSource;
this.rememberSourceAsCard = ability.rememberSourceAsCard;
this.fromZone = ability.fromZone;
}
@Override
@ -67,22 +75,20 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getPlayerId().equals(this.getControllerId())) {
Spell spell = game.getStack().getSpell(event.getTargetId());
if (filter.match(spell, getControllerId(), this, game)) {
this.getEffects().setValue("spellCast", spell);
if (rememberSource) {
if (rememberSourceAsCard) {
this.getEffects().setTargetPointer(new FixedTarget(spell.getCard().getId(), game));
} else {
this.getEffects().setTargetPointer(new FixedTarget(spell.getId(), game));
}
}
return true;
}
if (!event.getPlayerId().equals(this.getControllerId())) {
return false;
}
return false;
Spell spell = game.getStack().getSpell(event.getTargetId());
if (spell == null
|| !filter.match(spell, getControllerId(), this, game)
|| !(fromZone == Zone.ALL || fromZone == spell.getFromZone())) {
return false;
}
this.getEffects().setValue("spellCast", spell);
if (rememberSource) {
this.getEffects().setTargetPointer(new FixedTarget(rememberSourceAsCard ? spell.getCard().getId() : spell.getId(), game));
}
return true;
}
@Override

View file

@ -18,37 +18,60 @@ public class SpellCastOpponentTriggeredAbility extends TriggeredAbilityImpl {
protected FilterSpell filter;
protected SetTargetPointer setTargetPointer;
private final boolean onlyFromNonHand;
public SpellCastOpponentTriggeredAbility(Effect effect, boolean optional) {
this(effect, StaticFilters.FILTER_SPELL_A, optional);
this(effect, optional, false);
}
public SpellCastOpponentTriggeredAbility(Effect effect, boolean optional, boolean onlyFromNonHand) {
this(effect, StaticFilters.FILTER_SPELL_A, optional, onlyFromNonHand);
}
public SpellCastOpponentTriggeredAbility(Effect effect, FilterSpell filter, boolean optional) {
this(Zone.BATTLEFIELD, effect, filter, optional);
this(effect, filter, optional, false);
}
public SpellCastOpponentTriggeredAbility(Effect effect, FilterSpell filter, boolean optional, boolean onlyFromNonHand) {
this(Zone.BATTLEFIELD, effect, filter, optional, onlyFromNonHand);
}
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional) {
this(zone, effect, filter, optional, SetTargetPointer.NONE);
this(zone, effect, filter, optional, false);
}
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, boolean onlyFromNonHand) {
this(zone, effect, filter, optional, SetTargetPointer.NONE, onlyFromNonHand);
}
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, SetTargetPointer setTargetPointer) {
this(zone, effect, filter, optional, setTargetPointer, false);
}
/**
* @param zone
* @param effect
* @param filter
* @param optional
* @param setTargetPointer Supported: SPELL, PLAYER
* @param zone The zone in which the source permanent has to be in for the ability to trigger
* @param effect The effect to apply if condition is met
* @param filter Filter for matching the spell cast
* @param optional Whether the player can choose to apply the effect
* @param onlyFromNonHand Whether to trigger only when spells are cast from not the hand
* @param setTargetPointer Supported: SPELL, PLAYER
*/
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, SetTargetPointer setTargetPointer) {
public SpellCastOpponentTriggeredAbility(Zone zone, Effect effect, FilterSpell filter, boolean optional, SetTargetPointer setTargetPointer, boolean onlyFromNonHand) {
super(zone, effect, optional);
this.filter = filter;
this.setTargetPointer = setTargetPointer;
setTriggerPhrase("Whenever an opponent casts " + filter.getMessage() + ", ");
this.onlyFromNonHand = onlyFromNonHand;
setTriggerPhrase("Whenever an opponent casts "
+ filter.getMessage()
+ (onlyFromNonHand ? " from anywhere other than their hand" : "")
+ ", ");
}
public SpellCastOpponentTriggeredAbility(final SpellCastOpponentTriggeredAbility ability) {
super(ability);
this.filter = ability.filter;
this.setTargetPointer = ability.setTargetPointer;
this.onlyFromNonHand = ability.onlyFromNonHand;
}
@Override
@ -65,6 +88,11 @@ public class SpellCastOpponentTriggeredAbility extends TriggeredAbilityImpl {
if (!filter.match(spell, getControllerId(), this, game)) {
return false;
}
if (onlyFromNonHand && spell.getFromZone() == Zone.HAND) {
return false;
}
getEffects().setValue("spellCast", spell);
switch (setTargetPointer) {
case NONE:

View file

@ -0,0 +1,91 @@
package mage.abilities.common;
import mage.abilities.TriggeredAbilityImpl;
import mage.abilities.effects.Effect;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.targetpointer.FixedTarget;
/**
* "Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls,"
* AND
* "Whenever you become the target of a spell or ability an opponent controls,"
*
* @author Alex-Vasile
*/
public class TargetOfOpponentsSpellOrAbilityTriggeredAbility extends TriggeredAbilityImpl {
private Boolean onlyController = Boolean.FALSE;
public TargetOfOpponentsSpellOrAbilityTriggeredAbility(Effect effect) {
this(effect, false);
}
// NOTE: Using Boolean instead of boolean in order to have a second constructor with (Effect, "boolean") signature
public TargetOfOpponentsSpellOrAbilityTriggeredAbility(Effect effect, Boolean onlyController) {
this(effect, false, onlyController);
}
public TargetOfOpponentsSpellOrAbilityTriggeredAbility(Effect effect, boolean optional) {
this(effect, optional, Boolean.FALSE);
}
public TargetOfOpponentsSpellOrAbilityTriggeredAbility(Effect effect, boolean optional, Boolean onlyController) {
super(Zone.BATTLEFIELD, effect, optional);
this.onlyController = onlyController;
if (this.onlyController) {
setTriggerPhrase("Whenever you become the target of a spell or ability an opponent controls, ");
} else {
setTriggerPhrase("Whenever you or a permanent you control becomes the target of a spell or ability an opponent controls, ");
}
}
private TargetOfOpponentsSpellOrAbilityTriggeredAbility(final TargetOfOpponentsSpellOrAbilityTriggeredAbility ability) {
super(ability);
this.onlyController = ability.onlyController;
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.TARGETED;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
Player controller = game.getPlayer(this.getControllerId());
Player targetter = game.getPlayer(event.getPlayerId());
if (controller == null || targetter == null || controller.getId().equals(targetter.getId())) {
return false;
}
// Check if player was targeted
if (controller.getId().equals(event.getTargetId())) {
// Add target for effects which need it (e.g. the counter effect from AmuletOfSafekeeping)
this.getEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game));
return true;
}
// If only the controller is
if (this.onlyController) {
return false;
}
// Check if permanent was targeted
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getTargetId());
if (permanent == null || !controller.getId().equals(permanent.getControllerId())) {
return false;
}
// Add target for effects which need it (e.g. the counter effect from AmuletOfSafekeeping)
this.getEffects().setTargetPointer(new FixedTarget(event.getSourceId(), game));
return false;
}
@Override
public TargetOfOpponentsSpellOrAbilityTriggeredAbility copy() {
return new TargetOfOpponentsSpellOrAbilityTriggeredAbility(this);
}
}

View file

@ -0,0 +1,25 @@
package mage.abilities.condition.common;
import mage.abilities.Ability;
import mage.abilities.condition.Condition;
import mage.counters.Counter;
import mage.game.Game;
import mage.game.permanent.Permanent;
public enum SourceHasCountersCondition implements Condition {
instance;
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent == null) {
return false;
}
return permanent
.getCounters(game)
.values()
.stream()
.mapToInt(Counter::getCount)
.anyMatch(x -> x > 0);
}
}

View file

@ -3,6 +3,7 @@ package mage.abilities.costs.common;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.CostImpl;
import mage.cards.Card;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
import mage.constants.Outcome;
@ -11,6 +12,9 @@ import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.TargetCard;
import mage.target.TargetObject;
import mage.target.TargetPermanent;
import mage.util.CardUtil;
@ -23,19 +27,19 @@ import java.util.UUID;
*/
public class RemoveCounterCost extends CostImpl {
protected final TargetPermanent target;
protected final Target target;
private final CounterType counterTypeToRemove;
protected final int countersToRemove;
public RemoveCounterCost(TargetPermanent target) {
public RemoveCounterCost(Target target) {
this(target, null);
}
public RemoveCounterCost(TargetPermanent target, CounterType counterTypeToRemove) {
public RemoveCounterCost(Target target, CounterType counterTypeToRemove) {
this(target, counterTypeToRemove, 1);
}
public RemoveCounterCost(TargetPermanent target, CounterType counterTypeToRemove, int countersToRemove) {
public RemoveCounterCost(Target target, CounterType counterTypeToRemove, int countersToRemove) {
this.target = target;
this.counterTypeToRemove = counterTypeToRemove;
this.countersToRemove = countersToRemove;
@ -50,70 +54,94 @@ public class RemoveCounterCost extends CostImpl {
this.counterTypeToRemove = cost.counterTypeToRemove;
}
// TODO: TayamLuminousEnigmaCost can be simplified
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
paid = false;
int countersRemoved = 0;
Player controller = game.getPlayer(controllerId);
if (controller != null) {
if (countersToRemove == 0) { // Can happen when used for X costs where X = 0;
return paid = true;
}
target.clearChosen();
if (target.choose(Outcome.UnboostCreature, controllerId, source.getSourceId(), source, game)) {
for (UUID targetId : target.getTargets()) {
Permanent permanent = game.getPermanent(targetId);
if (permanent != null) {
if (!permanent.getCounters(game).isEmpty() && (counterTypeToRemove == null || permanent.getCounters(game).containsKey(counterTypeToRemove))) {
String counterName = null;
if (controller == null) {
return false;
}
if (countersToRemove == 0) { // Can happen when used for X costs where X = 0;
paid = true;
return paid;
}
target.clearChosen();
if (counterTypeToRemove != null) {
counterName = counterTypeToRemove.getName();
} else if (permanent.getCounters(game).size() > 1 && counterTypeToRemove == null) {
Choice choice = new ChoiceImpl(true);
Set<String> choices = new HashSet<>();
for (Counter counter : permanent.getCounters(game).values()) {
if (permanent.getCounters(game).getCount(counter.getName()) > 0) {
choices.add(counter.getName());
}
}
choice.setChoices(choices);
choice.setMessage("Choose a counter to remove from " + permanent.getLogName());
if (!controller.choose(Outcome.UnboostCreature, choice, game)) {
return false;
}
counterName = choice.getChoice();
} else {
for (Counter counter : permanent.getCounters(game).values()) {
if (counter.getCount() > 0) {
counterName = counter.getName();
}
}
}
if (counterName != null && !counterName.isEmpty()) {
int countersLeft = countersToRemove - countersRemoved;
int countersOnPermanent = permanent.getCounters(game).getCount(counterName);
int numberOfCountersSelected = 1;
if (countersLeft > 1 && countersOnPermanent > 1) {
numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent),
new StringBuilder("Remove how many counters from ").append(permanent.getIdName()).toString(), game);
}
permanent.removeCounters(counterName, numberOfCountersSelected, source, game);
countersRemoved += numberOfCountersSelected;
if (!game.isSimulation()) {
game.informPlayers(new StringBuilder(controller.getLogName())
.append(" removes ").append(numberOfCountersSelected == 1 ? "a" : numberOfCountersSelected).append(' ')
.append(counterName).append(numberOfCountersSelected == 1 ? " counter from " : " counters from ")
.append(permanent.getName()).toString());
}
if (countersRemoved == countersToRemove) {
this.paid = true;
break;
}
}
}
Outcome outcome;
if (target instanceof TargetPermanent) {
outcome = Outcome.UnboostCreature;
} else if (target instanceof TargetCard) { // For Mari, the Killing Quill
outcome = Outcome.Neutral;
} else {
throw new RuntimeException(
"Wrong target type provided for RemoveCounterCost. Provided " + target.getClass() + ". " +
"From ability " + ability);
}
if (!target.choose(outcome, controllerId, source.getSourceId(), source, game)) {
return paid;
}
for (UUID targetId : target.getTargets()) {
Card targetObject;
if (target instanceof TargetPermanent) {
targetObject = game.getPermanent(targetId);
} else { // For Mari, the Killing Quill
targetObject = game.getCard(targetId);
}
if (targetObject == null
|| targetObject.getCounters(game).isEmpty()
|| !(counterTypeToRemove == null || targetObject.getCounters(game).containsKey(counterTypeToRemove))) {
continue;
}
String counterName = null;
if (counterTypeToRemove != null) { // Counter type specified
counterName = counterTypeToRemove.getName();
} else if (targetObject.getCounters(game).size() == 1) { // Only one counter of creature
for (Counter counter : targetObject.getCounters(game).values()) {
if (counter.getCount() > 0) {
counterName = counter.getName();
}
}
} else { // Multiple counters, player much choose which type to remove from
Choice choice = new ChoiceImpl(true);
Set<String> choices = new HashSet<>();
for (Counter counter : targetObject.getCounters(game).values()) {
if (targetObject.getCounters(game).getCount(counter.getName()) > 0) {
choices.add(counter.getName());
}
}
choice.setChoices(choices);
choice.setMessage("Choose a counter to remove from " + targetObject.getLogName());
if (!controller.choose(Outcome.UnboostCreature, choice, game)) {
return false;
}
counterName = choice.getChoice();
}
if (counterName != null && !counterName.isEmpty()) {
int countersLeft = countersToRemove - countersRemoved;
int countersOnPermanent = targetObject.getCounters(game).getCount(counterName);
int numberOfCountersSelected = 1;
if (countersLeft > 1 && countersOnPermanent > 1) {
numberOfCountersSelected = controller.getAmount(1, Math.min(countersLeft, countersOnPermanent),
"Remove how many counters from " + targetObject.getIdName(), game);
}
targetObject.removeCounters(counterName, numberOfCountersSelected, source, game);
countersRemoved += numberOfCountersSelected;
if (!game.isSimulation()) {
game.informPlayers(controller.getLogName() +
" removes " + (numberOfCountersSelected == 1 ? "a" : numberOfCountersSelected) + ' ' +
counterName + (numberOfCountersSelected == 1 ? " counter from " : " counters from ") +
targetObject.getName());
}
if (countersRemoved == countersToRemove) {
this.paid = true;
break;
}
}
}

View file

@ -0,0 +1,164 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.Mode;
import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CommanderCardType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.common.FilterCreatureCard;
import mage.game.Game;
import mage.players.Player;
import mage.target.TargetCard;
import mage.target.common.TargetCardInCommandZone;
import mage.target.common.TargetCardInGraveyard;
import mage.target.common.TargetCardInHand;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
/**
* "Put a {filter} card from {zone 1} or {zone 2} onto the battlefield.
*
* @author TheElk801, Alex-Vasile
*/
public class PutCardFromOneOfTwoZonesOntoBattlefieldEffect extends OneShotEffect {
private final FilterCard filterCard;
private final boolean tapped;
private final Effect effectToApplyOnPermanent;
private final Zone zone1;
private final Zone zone2;
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard) {
this(filterCard, false);
}
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, boolean tapped) {
this(filterCard, tapped, null);
}
/**
*
* @param filterCard Filter used to filter which cards are valid choices. (no default)
* @param tapped If the permanent should enter the battlefield tapped (default is False)
* @param effectToApplyOnPermanent An effect to apply to the permanent after it enters (default null)
* See "Swift Warkite" or "Nissa of Shadowed Boughs".
* @param zone1 The first zone to pick from (default of HAND)
* @param zone2 The second zone to pick from (defualt of GRAVEYARD)
*/
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, boolean tapped, Effect effectToApplyOnPermanent, Zone zone1, Zone zone2) {
super(filterCard instanceof FilterCreatureCard ? Outcome.PutCreatureInPlay : Outcome.PutCardInPlay);
this.filterCard = filterCard;
this.tapped = tapped;
this.effectToApplyOnPermanent = effectToApplyOnPermanent;
this.zone1 = zone1;
this.zone2 = zone2;
}
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, boolean tapped, Effect effectToApplyOnPermanent) {
this(filterCard, tapped, effectToApplyOnPermanent, Zone.HAND, Zone.GRAVEYARD);
}
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect(FilterCard filterCard, Zone zone1, Zone zone2) {
this(filterCard, false, null, zone1, zone2);
}
private PutCardFromOneOfTwoZonesOntoBattlefieldEffect(final PutCardFromOneOfTwoZonesOntoBattlefieldEffect effect) {
super(effect);
this.filterCard = effect.filterCard;
this.tapped = effect.tapped;
this.zone1 = effect.zone1;
this.zone2 = effect.zone2;
if (effect.effectToApplyOnPermanent != null) {
this.effectToApplyOnPermanent = effect.effectToApplyOnPermanent.copy();
} else {
this.effectToApplyOnPermanent = null;
}
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller == null) {
return false;
}
Cards cardsInZone1 = getCardsFromZone(game, controller, zone1);
Cards cardsInZone2 = getCardsFromZone(game, controller, zone2);
boolean cardsAvailableInZone1 = cardsInZone1.count(filterCard, game) > 0;
boolean cardsAvailableInZone2 = cardsInZone2.count(filterCard, game) > 0;
if (!cardsAvailableInZone1 && !cardsAvailableInZone2) {
return false;
}
boolean choose1stZone;
if (cardsAvailableInZone1 && cardsAvailableInZone2) {
choose1stZone = controller.chooseUse(outcome, "Where do you want to chose the card from?",
null, zone1.name(), zone2.name(), source, game);
} else {
choose1stZone = cardsAvailableInZone1;
}
Zone zone = choose1stZone ? zone1 : zone2;
Cards cards = choose1stZone ? cardsInZone1 : cardsInZone2;
TargetCard targetCard;
switch (zone) {
case HAND:
targetCard = new TargetCardInHand(filterCard);
break;
case GRAVEYARD:
targetCard = new TargetCardInGraveyard(filterCard);
break;
case COMMAND:
targetCard = new TargetCardInCommandZone(filterCard);
break;
default:
return false;
}
controller.choose(outcome, cards, targetCard, game);
Card card = game.getCard(targetCard.getFirstTarget());
if (card == null || !controller.moveCards(card, Zone.BATTLEFIELD, source, game, tapped, false, false, null)) {
return false;
}
if (effectToApplyOnPermanent != null) {
effectToApplyOnPermanent.setTargetPointer(new FixedTarget(card.getId()));
effectToApplyOnPermanent.apply(game, source);
}
return true;
}
private static Cards getCardsFromZone(Game game, Player player, Zone zone) {
switch (zone) {
case HAND:
return player.getHand();
case COMMAND:
return new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY));
case GRAVEYARD:
return player.getGraveyard();
default:
return new CardsImpl();
}
}
@Override
public PutCardFromOneOfTwoZonesOntoBattlefieldEffect copy() {
return new PutCardFromOneOfTwoZonesOntoBattlefieldEffect(this);
}
@Override
public String getText(Mode mode) {
return "you may put " + CardUtil.addArticle(this.filterCard.getMessage()) +
" from your hand or graveyard onto the battlefield" +
(this.tapped ? " tapped" : "") +
(effectToApplyOnPermanent == null ? "" : ". " + effectToApplyOnPermanent.getText(mode));
}
}

View file

@ -0,0 +1,49 @@
package mage.abilities.effects.common;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.cards.Card;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
/**
* Used for "Return {this} to the battlefield attached to that creature at the beginning of the next end step" abilities.
* E.g.g Gift of Immortality and Next of Kin.
*
* @author LevelX2, Alex-Vasile
*/
public class ReturnToBattlefieldAttachedEffect extends OneShotEffect {
public ReturnToBattlefieldAttachedEffect() {
super(Outcome.PutCardInPlay);
staticText = "Return {this} to the battlefield attached to that creature at the beginning of the next end step";
}
public ReturnToBattlefieldAttachedEffect(final ReturnToBattlefieldAttachedEffect effect) {
super(effect);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent creature = game.getPermanent(getTargetPointer().getFirst(game, source));
Card aura = game.getCard(source.getSourceId());
if (controller == null
|| creature == null
|| aura == null || game.getState().getZone(aura.getId()) != Zone.GRAVEYARD) {
return false;
}
game.getState().setValue("attachTo:" + aura.getId(), creature);
controller.moveCards(aura, Zone.BATTLEFIELD, source, game);
return creature.addAttachment(aura.getId(), source, game);
}
@Override
public ReturnToBattlefieldAttachedEffect copy() {
return new ReturnToBattlefieldAttachedEffect(this);
}
}

View file

@ -0,0 +1,94 @@
package mage.abilities.effects.common.continuous;
import mage.MageObject;
import mage.abilities.Ability;
import mage.abilities.costs.Cost;
import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.keyword.ReplicateAbility;
import mage.constants.Duration;
import mage.constants.Layer;
import mage.constants.Outcome;
import mage.constants.SubLayer;
import mage.filter.FilterSpell;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.game.stack.Spell;
import mage.game.stack.StackObject;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author LevelX2, Alex-Vasile
*/
public class EachSpellYouCastHasReplicateEffect extends ContinuousEffectImpl {
private final FilterSpell filter;
private final Cost fixedNewCost;
private final Map<UUID, ReplicateAbility> replicateAbilities = new HashMap<>();
public EachSpellYouCastHasReplicateEffect(FilterSpell filter) {
this(filter, null);
}
/**
*
* @param filter Filter used for filtering spells
* @param fixedNewCost Fixed new cost to pay as the replication cost
*/
public EachSpellYouCastHasReplicateEffect(FilterSpell filter, Cost fixedNewCost) {
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
this.filter = filter;
this.fixedNewCost = fixedNewCost;
this.staticText = "Each " + this.filter.getMessage() + " you cast has replicate" +
(this.fixedNewCost == null ? ". The replicate cost is equal to its mana cost" : ' ' + this.fixedNewCost.getText())
+ ". <i>(When you cast it, copy it for each time you paid its replicate cost. You may choose new targets for the copies.)</i>";
}
private EachSpellYouCastHasReplicateEffect(final EachSpellYouCastHasReplicateEffect effect) {
super(effect);
this.filter = effect.filter;
this.fixedNewCost = effect.fixedNewCost;
this.replicateAbilities.putAll(effect.replicateAbilities);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = game.getPermanent(source.getSourceId());
if (permanent == null
|| !permanent.isControlledBy(source.getControllerId())) { // Verify that the controller of the permanent is the one who cast the spell
return false;
}
boolean applied = false;
for (StackObject stackObject : game.getStack()) {
if (!(stackObject instanceof Spell)
|| stackObject.isCopy()
|| !stackObject.isControlledBy(source.getControllerId())
|| (fixedNewCost == null && stackObject.getManaCost().isEmpty())) { // If the spell has no mana cost, it cannot be played by this ability unless an fixed alternative cost (e.g. such as from Threefold Signal) is specified.
continue;
}
Spell spell = (Spell) stackObject;
if (filter.match(stackObject, game)) {
Cost cost = fixedNewCost != null ? fixedNewCost.copy() : spell.getSpellAbility().getManaCosts().copy();
ReplicateAbility replicateAbility = replicateAbilities.computeIfAbsent(spell.getId(), k -> new ReplicateAbility(cost));
game.getState().addOtherAbility(spell.getCard(), replicateAbility, false); // Do not copy because paid and # of activations state is handled in the baility
applied = true;
}
}
if (game.getStack().isEmpty()) {
replicateAbilities.clear();
}
return applied;
}
@Override
public EachSpellYouCastHasReplicateEffect copy() {
return new EachSpellYouCastHasReplicateEffect(this);
}
}

View file

@ -3,6 +3,7 @@ package mage.abilities.effects.common.continuous;
import mage.abilities.Ability;
import mage.abilities.ActivatedAbility;
import mage.abilities.Mode;
import mage.abilities.condition.Condition;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.constants.Duration;
import mage.constants.Layer;
@ -24,6 +25,7 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
protected UUID controllingPlayerId;
private boolean fixedControl;
private boolean firstControlChange = true;
private final Condition condition;
public GainControlTargetEffect(Duration duration) {
this(duration, false, null);
@ -48,15 +50,21 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
}
public GainControlTargetEffect(Duration duration, boolean fixedControl, UUID controllingPlayerId) {
this(duration, fixedControl, controllingPlayerId, null);
}
public GainControlTargetEffect(Duration duration, boolean fixedControl, UUID controllingPlayerId, Condition condition) {
super(duration, Layer.ControlChangingEffects_2, SubLayer.NA, Outcome.GainControl);
this.controllingPlayerId = controllingPlayerId;
this.fixedControl = fixedControl;
this.condition = condition;
}
public GainControlTargetEffect(final GainControlTargetEffect effect) {
super(effect);
this.controllingPlayerId = effect.controllingPlayerId;
this.fixedControl = effect.fixedControl;
this.condition = effect.condition;
}
@Override
@ -106,6 +114,9 @@ public class GainControlTargetEffect extends ContinuousEffectImpl {
// This does not handle correctly multiple targets at once
discard();
}
if (condition != null && !condition.apply(game, source)) {
discard();
}
}
// no valid target exists and the controller is no longer in the game, effect can be discarded
if (!oneTargetStillExists || !controller.isInGame()) {

View file

@ -2,34 +2,55 @@ package mage.abilities.effects.common.counter;
import mage.abilities.Ability;
import mage.abilities.effects.OneShotEffect;
import mage.choices.Choice;
import mage.choices.ChoiceImpl;
import mage.constants.Outcome;
import mage.counters.Counter;
import mage.counters.CounterType;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* @author TheElk801
*/
public class AddCounterChoiceSourceEffect extends OneShotEffect {
private final CounterType counterType1;
private final CounterType counterType2;
private final List<CounterType> counterTypes;
public AddCounterChoiceSourceEffect(CounterType counterType1, CounterType counterType2) {
public AddCounterChoiceSourceEffect(CounterType ... counterTypes) {
super(Outcome.Benefit);
this.counterType1 = counterType1;
this.counterType2 = counterType2;
staticText = "with your choice of a " + counterType1 + " counter or a " + counterType2 + " counter on it";
this.counterTypes = Arrays.stream(counterTypes).collect(Collectors.toList());
this.createStaticText();
}
private AddCounterChoiceSourceEffect(final AddCounterChoiceSourceEffect effect) {
super(effect);
this.counterType1 = effect.counterType1;
this.counterType2 = effect.counterType2;
this.counterTypes = new ArrayList<>(effect.counterTypes);
}
private void createStaticText() {
switch (this.counterTypes.size()) {
case 0:
case 1:
throw new IllegalArgumentException("AddCounterChoiceSourceEffect should be called with at least 2 " +
"counter types, it was called with: " + this.counterTypes);
case 2:
this.staticText = "with your choice of a " + this.counterTypes.get(0) +
" counter or a " + this.counterTypes.get(1) + " counter on it";
break;
default:
List<String> strings = this.counterTypes.stream().map(CounterType::toString).collect(Collectors.toList());
this.staticText = CardUtil.concatWithOr(strings);
break;
}
}
@Override
@ -39,19 +60,30 @@ public class AddCounterChoiceSourceEffect extends OneShotEffect {
if (player == null || permanent == null) {
return false;
}
Counter counter;
if (player.chooseUse(
Outcome.Neutral, "Choose " + counterType1 + " or " + counterType2, null,
cap(counterType1.getName()), cap(counterType2.getName()), source, game
)) {
counter = counterType1.createInstance();
} else {
counter = counterType2.createInstance();
Choice counterChoice = new ChoiceImpl();
counterChoice.setMessage("Choose counter type");
counterChoice.setChoices(
this.counterTypes
.stream()
.map(counterType -> AddCounterChoiceSourceEffect.capitalize(counterType.getName()))
.collect(Collectors.toSet())
);
if (!player.choose(Outcome.Neutral, counterChoice, game)) {
return false;
}
CounterType counterChosen = CounterType.findByName(counterChoice.getChoice().toLowerCase(Locale.ENGLISH));
if (counterChosen == null || !this.counterTypes.contains(counterChosen)) {
return false;
}
Counter counter = counterChosen.createInstance();
return permanent.addCounters(counter, source.getControllerId(), source, game);
}
private static final String cap(String string) {
private static String capitalize(String string) {
return string != null ? string.substring(0, 1).toUpperCase(Locale.ENGLISH) + string.substring(1) : null;
}

View file

@ -1,87 +1,95 @@
package mage.abilities.keyword;
import mage.abilities.Ability;
import mage.abilities.SpellAbility;
import mage.abilities.StaticAbility;
import mage.abilities.common.delayed.ReflexiveTriggeredAbility;
import mage.abilities.costs.Cost;
import mage.abilities.costs.*;
import mage.abilities.costs.common.SacrificeTargetCost;
import mage.abilities.effects.common.CopySourceSpellEffect;
import mage.abilities.effects.common.InfoEffect;
import mage.cards.Card;
import mage.constants.ComparisonType;
import mage.constants.Outcome;
import mage.constants.Zone;
import mage.filter.common.FilterControlledCreaturePermanent;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.mageobject.PowerPredicate;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetControlledPermanent;
import java.util.UUID;
/**
* @author TheElk801
* @author TheElk801, Alex-Vasile
*/
public class CasualtyAbility extends StaticAbility {
public class CasualtyAbility extends StaticAbility implements OptionalAdditionalSourceCosts {
public CasualtyAbility(Card card, int number) {
super(Zone.ALL, new InfoEffect(
"Casualty " + number + " <i>(As you cast this spell, " +
"you may sacrifice a creature with power " + number +
" or greater. When you do, copy this spell.)</i>"
));
card.getSpellAbility().addCost(new CasualtyCost(number));
private static final String keywordText = "Casualty";
private final String promptString;
protected OptionalAdditionalCost additionalCost;
private static TargetControlledPermanent makeFilter(int number) {
FilterControlledPermanent filter = new FilterControlledCreaturePermanent(
"creature with power " + number + " or greater"
);
filter.add(new PowerPredicate(ComparisonType.MORE_THAN, number - 1));
return new TargetControlledPermanent(1, 1, filter, true);
}
public CasualtyAbility(int number) {
super(Zone.STACK, null);
String reminderText = "As you cast this spell, you may sacrifice a creature with power " +
number + " or greater. When you do, copy this spell.";
this.promptString = "Sacrifice a creature with power " + number + " or greater?";
this.additionalCost = new OptionalAdditionalCostImpl(keywordText, reminderText, new SacrificeTargetCost(makeFilter(number)));
this.additionalCost.setRepeatable(false);
this.setRuleAtTheTop(true);
}
private CasualtyAbility(final CasualtyAbility ability) {
super(ability);
this.additionalCost = ability.additionalCost;
this.promptString = ability.promptString;
}
public void resetCasualty() {
if (additionalCost != null) {
additionalCost.reset();
}
}
@Override
public CasualtyAbility copy() {
return new CasualtyAbility(this);
}
}
class CasualtyCost extends SacrificeTargetCost {
CasualtyCost(int number) {
super(new TargetControlledPermanent(0, 1, makeFilter(number), true));
this.text = "";
}
private CasualtyCost(final CasualtyCost cost) {
super(cost);
}
@Override
public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) {
if (!super.pay(ability, game, source, controllerId, noMana, costToPay)) {
return false;
public void addOptionalAdditionalCosts(Ability ability, Game game) {
if (!(ability instanceof SpellAbility)) {
return;
}
if (!getPermanents().isEmpty()) {
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(
new CopySourceSpellEffect(), false, "when you do, copy this spell"
), source);
Player player = game.getPlayer(ability.getControllerId());
if (player == null) {
return;
}
return true;
this.resetCasualty();
boolean canPay = additionalCost.canPay(ability, this, ability.getControllerId(), game);
if (!canPay || !player.chooseUse(Outcome.Sacrifice, promptString, ability, game)) {
return;
}
additionalCost.activate();
for (Cost cost : ((Costs<Cost>) additionalCost)) {
ability.getCosts().add(cost.copy());
}
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(
new CopySourceSpellEffect(), false, "when you do, copy this spell"
), ability);
}
@Override
public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) {
return true;
}
@Override
public CasualtyCost copy() {
return new CasualtyCost(this);
}
private static FilterControlledPermanent makeFilter(int number) {
FilterControlledPermanent filter = new FilterControlledCreaturePermanent(
"creature with power " + number + " or greater"
);
filter.add(new PowerPredicate(ComparisonType.MORE_THAN, number - 1));
return filter;
public String getCastMessageSuffix() {
return additionalCost == null ? "" : additionalCost.getCastSuffixMessage(0);
}
}

View file

@ -61,17 +61,11 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
@Override
public boolean isActivated() {
if (additionalCost != null) {
return additionalCost.isActivated();
}
return false;
return additionalCost != null && additionalCost.isActivated();
}
public int getActivateCount() {
if (additionalCost != null) {
return additionalCost.getActivateCount();
}
return 0;
return additionalCost == null ? 0 : additionalCost.getActivateCount();
}
public void resetReplicate() {
@ -82,36 +76,38 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
@Override
public void addOptionalAdditionalCosts(Ability ability, Game game) {
if (ability instanceof SpellAbility) {
Player player = game.getPlayer(ability.getControllerId());
if (player != null) {
this.resetReplicate();
boolean again = true;
while (player.canRespond()
&& again) {
String times = "";
if (additionalCost.isRepeatable()) {
int numActivations = additionalCost.getActivateCount();
times = (numActivations + 1) + (numActivations == 0 ? " time " : " times ");
}
if (!(ability instanceof SpellAbility)) {
return;
}
// TODO: add AI support to find max number of possible activations (from available mana)
// canPay checks only single mana available, not total mana usage
if (additionalCost.canPay(ability, this, ability.getControllerId(), game)
&& player.chooseUse(/*Outcome.Benefit*/Outcome.AIDontUseIt,
new StringBuilder("Pay ").append(times).append(
additionalCost.getText(false)).append(" ?").toString(), ability, game)) {
additionalCost.activate();
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
ability.getCosts().add(cost.copy());
}
}
Player player = game.getPlayer(ability.getControllerId());
if (player == null) {
return;
}
this.resetReplicate();
boolean again = true;
while (player.canRespond() && again) {
String times = "";
if (additionalCost.isRepeatable()) {
int numActivations = additionalCost.getActivateCount();
times = (numActivations + 1) + (numActivations == 0 ? " time " : " times ");
}
String payPrompt = "Pay " + times + additionalCost.getText(false) + " ?";
// TODO: add AI support to find max number of possible activations (from available mana)
// canPay checks only single mana available, not total mana usage
boolean canPay = additionalCost.canPay(ability, this, ability.getControllerId(), game);
if (!canPay || !player.chooseUse(/*Outcome.Benefit*/Outcome.AIDontUseIt, payPrompt, ability, game)) {
again = false;
} else {
additionalCost.activate();
for (Iterator it = ((Costs) additionalCost).iterator(); it.hasNext(); ) {
Cost cost = (Cost) it.next();
if (cost instanceof ManaCostsImpl) {
ability.getManaCostsToPay().add((ManaCostsImpl) cost.copy());
} else {
again = false;
ability.getCosts().add(cost.copy());
}
}
}
@ -120,29 +116,16 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
@Override
public String getRule() {
StringBuilder sb = new StringBuilder();
if (additionalCost != null) {
sb.append(additionalCost.getText(false));
sb.append(' ').append(additionalCost.getReminderText());
}
return sb.toString();
return additionalCost == null ? "" : additionalCost.getText(false) + ' ' + additionalCost.getReminderText();
}
@Override
public String getCastMessageSuffix() {
if (additionalCost != null) {
return additionalCost.getCastSuffixMessage(0);
} else {
return "";
}
return additionalCost == null ? "" : additionalCost.getCastSuffixMessage(0);
}
public String getReminderText() {
if (additionalCost != null) {
return additionalCost.getReminderText();
} else {
return "";
}
return additionalCost == null ? "" : additionalCost.getReminderText();
}
}
@ -169,24 +152,26 @@ class ReplicateTriggeredAbility extends TriggeredAbilityImpl {
@Override
public boolean checkTrigger(GameEvent event, Game game) {
if (event.getSourceId().equals(this.sourceId)) {
StackObject spell = game.getStack().getStackObject(this.sourceId);
if (spell instanceof Spell) {
Card card = ((Spell) spell).getCard();
if (card != null) {
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof ReplicateAbility) {
if (ability.isActivated()) {
for (Effect effect : this.getEffects()) {
effect.setValue("ReplicateSpell", spell);
effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount());
}
return true;
}
}
}
}
if (!event.getSourceId().equals(this.sourceId)) {
return false;
}
StackObject spell = game.getStack().getStackObject(this.sourceId);
if (!(spell instanceof Spell)) {
return false;
}
Card card = ((Spell) spell).getCard();
if (card == null) {
return false;
}
for (Ability ability : card.getAbilities(game)) {
if (!(ability instanceof ReplicateAbility) || !ability.isActivated()) {
continue;
}
for (Effect effect : this.getEffects()) {
effect.setValue("ReplicateSpell", spell);
effect.setValue("ReplicateCount", ((ReplicateAbility) ability).getActivateCount());
}
return true;
}
return false;
}
@ -211,29 +196,26 @@ class ReplicateCopyEffect extends OneShotEffect {
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
if (controller != null) {
Spell spell = (Spell) this.getValue("ReplicateSpell");
int replicateCount = (Integer) this.getValue("ReplicateCount");
if (spell != null
&& replicateCount > 0) {
// reset replicate now so the copies don't report x times Replicate
Card card = game.getCard(spell.getSourceId());
if (card != null) {
for (Ability ability : card.getAbilities(game)) {
if (ability instanceof ReplicateAbility) {
if (ability.isActivated()) {
((ReplicateAbility) ability).resetReplicate();
}
}
}
}
// create the copies
spell.createCopyOnStack(game, source, source.getControllerId(), true, replicateCount);
return true;
}
Spell spell = (Spell) this.getValue("ReplicateSpell");
int replicateCount = (Integer) this.getValue("ReplicateCount");
if (controller == null || spell == null || replicateCount == 0) {
return false;
}
return false;
// reset replicate now so the copies don't report x times Replicate
Card card = game.getCard(spell.getSourceId());
if (card == null) {
return false;
}
for (Ability ability : card.getAbilities(game)) {
if ((ability instanceof ReplicateAbility) && ability.isActivated()) {
((ReplicateAbility) ability).resetReplicate();
}
}
// create the copies
spell.createCopyOnStack(game, source, source.getControllerId(), true, replicateCount);
return true;
}
@Override

View file

@ -850,6 +850,21 @@ public final class StaticFilters {
FILTER_CREATURE_TOKEN.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_CONTROLLED_CREATURE_NON_TOKEN = new FilterCreaturePermanent("a nontoken creature you control");
static {
FILTER_CONTROLLED_CREATURE_NON_TOKEN.add(TargetController.YOU.getControllerPredicate());
FILTER_CONTROLLED_CREATURE_NON_TOKEN.add(TokenPredicate.FALSE);
FILTER_CONTROLLED_CREATURE_NON_TOKEN.setLockedFilter(true);
}
public static final FilterCreaturePermanent FILTER_CREATURE_NON_TOKEN = new FilterCreaturePermanent("a nontoken creature");
static {
FILTER_CREATURE_NON_TOKEN.add(TokenPredicate.FALSE);
FILTER_CREATURE_NON_TOKEN.setLockedFilter(true);
}
public static final FilterControlledCreaturePermanent FILTER_A_CONTROLLED_CREATURE_P1P1 = new FilterControlledCreaturePermanent("a creature you control with a +1/+1 counter on it");
static {

View file

@ -0,0 +1,31 @@
package mage.filter.predicate.card;
import mage.cards.Card;
import mage.filter.StaticFilters;
import mage.filter.predicate.Predicate;
import mage.game.Game;
/**
* @author Plopman, Alex-Vasile
*/
public class CardManaCostLessThanControlledLandCountPredicate implements Predicate<Card> {
private static final String string = "card with mana value less than or equal to the number of lands you control";
private static final CardManaCostLessThanControlledLandCountPredicate instance = new CardManaCostLessThanControlledLandCountPredicate();
private CardManaCostLessThanControlledLandCountPredicate() { }
public static CardManaCostLessThanControlledLandCountPredicate getInstance() {
return instance;
}
@Override
public boolean apply(Card input, Game game) {
return input.getManaValue() <= game.getBattlefield().getAllActivePermanents(StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND, input.getOwnerId(), game).size();
}
@Override
public String toString() {
return string;
}
}

View file

@ -0,0 +1,26 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
import mage.constants.ComparisonType;
import mage.filter.predicate.IntComparePredicate;
/**
* @author Alex-Vasile
*/
public class BasePowerPredicate extends IntComparePredicate<MageObject> {
public BasePowerPredicate(ComparisonType type, int value) {
super(type, value);
}
@Override
protected int getInputValue(MageObject input) {
return input.getPower().getModifiedBaseValue();
}
@Override
public String toString() {
return "Base power" + super.toString();
}
}

View file

@ -0,0 +1,23 @@
package mage.filter.predicate.mageobject;
import mage.MageObject;
import mage.constants.ComparisonType;
import mage.filter.predicate.IntComparePredicate;
public class BaseToughnessPredicate extends IntComparePredicate<MageObject> {
public BaseToughnessPredicate(ComparisonType type, int value) {
super(type, value);
}
@Override
protected int getInputValue(MageObject input) {
return input.getToughness().getModifiedBaseValue();
}
@Override
public String toString() {
return "Base toughness" + super.toString();
}
}

View file

@ -0,0 +1,87 @@
package mage.target.common;
import mage.abilities.Ability;
import mage.cards.Card;
import mage.cards.Cards;
import mage.cards.CardsImpl;
import mage.constants.CommanderCardType;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.game.Game;
import mage.game.events.TargetEvent;
import mage.players.Player;
import mage.target.TargetCard;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
/**
* Based on TargetCardInHand
* @author Alex-Vasile
*/
public class TargetCardInCommandZone extends TargetCard {
public TargetCardInCommandZone(FilterCard filter) {
super(1, 1, Zone.COMMAND, filter);
}
public TargetCardInCommandZone(final TargetCardInCommandZone targetCardInCommandZone) {
super(targetCardInCommandZone);
}
@Override
public TargetCardInCommandZone copy() {
return new TargetCardInCommandZone(this);
}
@Override
public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) {
Card card = game.getCard(id);
return game.getState().getZone(id) == Zone.COMMAND
&& game.getState().getPlayersInRange(getTargetController() == null ? playerId : getTargetController(), game).contains(game.getOwnerId(id))
&& filter.match(card, game);
}
@Override
public boolean canTarget(UUID id, Ability source, Game game) {
return this.canTarget(source.getControllerId(), id, source, game);
}
@Override
public Set<UUID> possibleTargets(UUID sourceControllerId, Ability source, Game game) {
Set<UUID> possibleTargets = new HashSet<>();
Player player = game.getPlayer(sourceControllerId);
if (player == null) {
return possibleTargets;
}
Cards cards = new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY));
for (Card card : cards.getCards(filter, sourceControllerId, source, game)) {
if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) {
possibleTargets.add(card.getId());
}
}
return possibleTargets;
}
@Override
public boolean canChoose(UUID sourceControllerId, Ability source, Game game) {
Player player = game.getPlayer(sourceControllerId);
if (player == null) {
return false;
}
int possibletargets = 0;
Cards cards = new CardsImpl(game.getCommanderCardsFromCommandZone(player, CommanderCardType.ANY));
for (Card card : cards.getCards(filter, sourceControllerId, source, game)) {
if (source == null || source.getSourceId() == null || isNotTarget() || !game.replaceEvent(new TargetEvent(card, source.getSourceId(), sourceControllerId))) {
possibletargets++;
if (possibletargets >= this.minNumberOfTargets) {
return true;
}
}
}
return false;
}
}

Some files were not shown because too many files have changed in this diff Show more