mirror of
https://github.com/correl/mage.git
synced 2024-11-28 19:19:55 +00:00
[NCC] Implement several cards (#9328)
Many associated refactors too. See full PR for detail.
This commit is contained in:
parent
b7151cfa58
commit
fd16f2a16b
104 changed files with 6091 additions and 1069 deletions
|
@ -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));
|
||||
|
|
93
Mage.Sets/src/mage/cards/a/AerialExtortionist.java
Normal file
93
Mage.Sets/src/mage/cards/a/AerialExtortionist.java
Normal 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);
|
||||
}
|
||||
}
|
119
Mage.Sets/src/mage/cards/a/AgentsToolkit.java
Normal file
119
Mage.Sets/src/mage/cards/a/AgentsToolkit.java
Normal 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);
|
||||
|
||||
// Agent’s 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 Agent’s Toolkit onto that creature.
|
||||
this.addAbility(new EntersBattlefieldControlledTriggeredAbility(
|
||||
new AgentToolkitMoveCounterEffect(),
|
||||
StaticFilters.FILTER_PERMANENT_CREATURE)
|
||||
);
|
||||
|
||||
// {2}, Sacrifice Agent’s 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);
|
||||
}
|
||||
}
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
|
140
Mage.Sets/src/mage/cards/a/AnheloThePainter.java
Normal file
140
Mage.Sets/src/mage/cards/a/AnheloThePainter.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
68
Mage.Sets/src/mage/cards/b/BessSoulNourisher.java
Normal file
68
Mage.Sets/src/mage/cards/b/BessSoulNourisher.java
Normal 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);
|
||||
}
|
||||
}
|
107
Mage.Sets/src/mage/cards/c/CrypticPursuit.java
Normal file
107
Mage.Sets/src/mage/cards/c/CrypticPursuit.java
Normal 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 it’s 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 it’s 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);
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
98
Mage.Sets/src/mage/cards/d/DenryKlinEditorInChief.java
Normal file
98
Mage.Sets/src/mage/cards/d/DenryKlinEditorInChief.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
72
Mage.Sets/src/mage/cards/f/FamilysFavor.java
Normal file
72
Mage.Sets/src/mage/cards/f/FamilysFavor.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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) {
|
||||
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) {
|
||||
if (card == null || enchanted == null || card.getZoneChangeCounter(game) != enchanted.getZoneChangeCounter(game) + 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
if (permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create delayed triggered ability
|
||||
Effect effect = new ReturnToBattlefieldAttachedEffect();
|
||||
effect.setTargetPointer(new FixedTarget(permanent, game));
|
||||
game.addDelayedTriggeredAbility(new AtTheBeginOfNextEndStepDelayedTriggeredAbility(effect), source);
|
||||
}
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GiftOfImmortalityReturnEnchantmentEffect copy() {
|
||||
return new GiftOfImmortalityReturnEnchantmentEffect(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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)));
|
||||
|
|
203
Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java
Normal file
203
Mage.Sets/src/mage/cards/m/MariTheKillingQuill.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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 don’t control.
|
||||
this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
106
Mage.Sets/src/mage/cards/n/NextOfKin.java
Normal file
106
Mage.Sets/src/mage/cards/n/NextOfKin.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
120
Mage.Sets/src/mage/cards/o/OskarRubbishReclaimer.java
Normal file
120
Mage.Sets/src/mage/cards/o/OskarRubbishReclaimer.java
Normal 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";
|
||||
}
|
||||
}
|
118
Mage.Sets/src/mage/cards/p/ParnesseTheSubtleBrush.java
Normal file
118
Mage.Sets/src/mage/cards/p/ParnesseTheSubtleBrush.java
Normal 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);
|
||||
}
|
||||
}
|
124
Mage.Sets/src/mage/cards/p/PhabineBosssConfidant.java
Normal file
124
Mage.Sets/src/mage/cards/p/PhabineBosssConfidant.java
Normal 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);
|
||||
}
|
||||
}
|
160
Mage.Sets/src/mage/cards/r/RainOfRiches.java
Normal file
160
Mage.Sets/src/mage/cards/r/RainOfRiches.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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.";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
if (controller == null || targetPermanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return PlayFromNotOwnHandZoneTargetEffect.exileAndPlayFromExile(game, source, targetPermanent,
|
||||
TargetController.OWNER, Duration.Custom, true, false, true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
197
Mage.Sets/src/mage/cards/r/ResourcefulDefense.java
Normal file
197
Mage.Sets/src/mage/cards/r/ResourcefulDefense.java
Normal 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;
|
||||
}
|
||||
}
|
53
Mage.Sets/src/mage/cards/r/RiveteersConfluence.java
Normal file
53
Mage.Sets/src/mage/cards/r/RiveteersConfluence.java
Normal 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 don’t 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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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 player’s library.exile the top card of each player’s library.
|
||||
|
|
63
Mage.Sets/src/mage/cards/s/ShieldBroker.java
Normal file
63
Mage.Sets/src/mage/cards/s/ShieldBroker.java
Normal 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 don’t 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);
|
||||
}
|
||||
}
|
126
Mage.Sets/src/mage/cards/s/SinisterConcierge.java
Normal file
126
Mage.Sets/src/mage/cards/s/SinisterConcierge.java
Normal 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);
|
||||
}
|
||||
}
|
127
Mage.Sets/src/mage/cards/s/SkywayRobber.java
Normal file
127
Mage.Sets/src/mage/cards/s/SkywayRobber.java
Normal 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);
|
||||
}
|
||||
}
|
91
Mage.Sets/src/mage/cards/s/SmugglersBuggy.java
Normal file
91
Mage.Sets/src/mage/cards/s/SmugglersBuggy.java
Normal 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 Smuggler’s Buggy deals combat damage to a player, you may cast the exiled card without paying its mana cost.
|
||||
// If you do, return Smuggler’s Buggy to its owner’s 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
136
Mage.Sets/src/mage/cards/s/SwindlersScheme.java
Normal file
136
Mage.Sets/src/mage/cards/s/SwindlersScheme.java
Normal 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);
|
||||
}
|
||||
}
|
188
Mage.Sets/src/mage/cards/s/SyrixCarrierOfTheFlame.java
Normal file
188
Mage.Sets/src/mage/cards/s/SyrixCarrierOfTheFlame.java
Normal 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();
|
||||
}
|
||||
}
|
122
Mage.Sets/src/mage/cards/t/TenuousTruce.java
Normal file
122
Mage.Sets/src/mage/cards/t/TenuousTruce.java
Normal 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 opponent’s 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);
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
81
Mage.Sets/src/mage/cards/t/ThreefoldSignal.java
Normal file
81
Mage.Sets/src/mage/cards/t/ThreefoldSignal.java
Normal 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 that’s 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 isn’t 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";
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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}.";
|
||||
}
|
||||
}
|
132
Mage.Sets/src/mage/cards/v/VaziKeenNegotiator.java
Normal file
132
Mage.Sets/src/mage/cards/v/VaziKeenNegotiator.java
Normal 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";
|
||||
}
|
||||
}
|
72
Mage.Sets/src/mage/cards/w/WaveOfRats.java
Normal file
72
Mage.Sets/src/mage/cards/w/WaveOfRats.java
Normal 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 owner’s 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);
|
||||
}
|
||||
}
|
234
Mage.Sets/src/mage/cards/w/WeatheredSentinels.java
Normal file
234
Mage.Sets/src/mage/cards/w/WeatheredSentinels.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -70,24 +70,25 @@ class WildMagicSorcererGainCascadeFirstSpellCastFromExileEffect extends Continuo
|
|||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Player controller = game.getPlayer(source.getControllerId());
|
||||
if (controller != null) {
|
||||
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;
|
||||
WildMagicSorcererWatcher watcher = game.getState().getWatcher(WildMagicSorcererWatcher.class);
|
||||
if (watcher != null
|
||||
&& FirstSpellCastFromExileEachTurnCondition.instance.apply(game, source)) {
|
||||
|
||||
if (FirstSpellCastFromExileEachTurnCondition.instance.apply(game, source)) {
|
||||
game.getState().addOtherAbility(spell.getCard(), cascadeAbility);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
enum FirstSpellCastFromExileEachTurnCondition implements Condition {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 isn’t 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 Creature—Instant
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 don’t 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);
|
||||
}
|
||||
}
|
|
@ -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 doesn’t 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 that’s 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 half’s characteristics are treated as though they didn’t 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);
|
||||
}
|
||||
}
|
|
@ -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 didn’t 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();
|
||||
}
|
||||
}
|
|
@ -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 it’s a nonland card, you may cast it by paying {1} rather than paying its mana cost.
|
||||
// If it’s 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 you’ve 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);
|
||||
}
|
||||
}
|
|
@ -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,12 +34,24 @@ 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;
|
||||
}
|
||||
|
||||
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
|
||||
public EnchantedPlayerAttackedTriggeredAbility copy() {
|
||||
return new EnchantedPlayerAttackedTriggeredAbility(this);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
if (counter > 0) {
|
||||
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 (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;
|
||||
}
|
||||
|
|
|
@ -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,23 +75,21 @@ public class SpellCastControllerTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (event.getPlayerId().equals(this.getControllerId())) {
|
||||
if (!event.getPlayerId().equals(this.getControllerId())) {
|
||||
return false;
|
||||
}
|
||||
Spell spell = game.getStack().getSpell(event.getTargetId());
|
||||
if (filter.match(spell, getControllerId(), this, game)) {
|
||||
if (spell == null
|
||||
|| !filter.match(spell, getControllerId(), this, game)
|
||||
|| !(fromZone == Zone.ALL || fromZone == spell.getFromZone())) {
|
||||
return false;
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
this.getEffects().setTargetPointer(new FixedTarget(rememberSourceAsCard ? spell.getCard().getId() : spell.getId(), game));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
|
|
|
@ -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 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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,61 +54,89 @@ 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 (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)) {
|
||||
if (controller == null) {
|
||||
return false;
|
||||
}
|
||||
counterName = choice.getChoice();
|
||||
if (countersToRemove == 0) { // Can happen when used for X costs where X = 0;
|
||||
paid = true;
|
||||
return paid;
|
||||
}
|
||||
target.clearChosen();
|
||||
|
||||
Outcome outcome;
|
||||
if (target instanceof TargetPermanent) {
|
||||
outcome = Outcome.UnboostCreature;
|
||||
} else if (target instanceof TargetCard) { // For Mari, the Killing Quill
|
||||
outcome = Outcome.Neutral;
|
||||
} else {
|
||||
for (Counter counter : permanent.getCounters(game).values()) {
|
||||
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 = permanent.getCounters(game).getCount(counterName);
|
||||
int countersOnPermanent = targetObject.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);
|
||||
"Remove how many counters from " + targetObject.getIdName(), game);
|
||||
}
|
||||
permanent.removeCounters(counterName, numberOfCountersSelected, source, game);
|
||||
targetObject.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());
|
||||
game.informPlayers(controller.getLogName() +
|
||||
" removes " + (numberOfCountersSelected == 1 ? "a" : numberOfCountersSelected) + ' ' +
|
||||
counterName + (numberOfCountersSelected == 1 ? " counter from " : " counters from ") +
|
||||
targetObject.getName());
|
||||
}
|
||||
if (countersRemoved == countersToRemove) {
|
||||
this.paid = true;
|
||||
|
@ -112,10 +144,6 @@ public class RemoveCounterCost extends CostImpl {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paid;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
if (!getPermanents().isEmpty()) {
|
||||
game.fireReflexiveTriggeredAbility(new ReflexiveTriggeredAbility(
|
||||
new CopySourceSpellEffect(), false, "when you do, copy this spell"
|
||||
), source);
|
||||
}
|
||||
return true;
|
||||
), 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,25 +76,31 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
|
|||
|
||||
@Override
|
||||
public void addOptionalAdditionalCosts(Ability ability, Game game) {
|
||||
if (ability instanceof SpellAbility) {
|
||||
if (!(ability instanceof SpellAbility)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = game.getPlayer(ability.getControllerId());
|
||||
if (player != null) {
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.resetReplicate();
|
||||
boolean again = true;
|
||||
while (player.canRespond()
|
||||
&& again) {
|
||||
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
|
||||
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)) {
|
||||
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();
|
||||
|
@ -110,39 +110,22 @@ public class ReplicateAbility extends StaticAbility implements OptionalAdditiona
|
|||
ability.getCosts().add(cost.copy());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
again = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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,25 +152,27 @@ class ReplicateTriggeredAbility extends TriggeredAbilityImpl {
|
|||
|
||||
@Override
|
||||
public boolean checkTrigger(GameEvent event, Game game) {
|
||||
if (event.getSourceId().equals(this.sourceId)) {
|
||||
if (!event.getSourceId().equals(this.sourceId)) {
|
||||
return false;
|
||||
}
|
||||
StackObject spell = game.getStack().getStackObject(this.sourceId);
|
||||
if (spell instanceof Spell) {
|
||||
if (!(spell instanceof Spell)) {
|
||||
return false;
|
||||
}
|
||||
Card card = ((Spell) spell).getCard();
|
||||
if (card != null) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
}
|
||||
for (Ability ability : card.getAbilities(game)) {
|
||||
if (ability instanceof ReplicateAbility) {
|
||||
if (ability.isActivated()) {
|
||||
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,31 +196,28 @@ 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) {
|
||||
if (controller == null || spell == null || replicateCount == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reset replicate now so the copies don't report x times Replicate
|
||||
Card card = game.getCard(spell.getSourceId());
|
||||
if (card != null) {
|
||||
if (card == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Ability ability : card.getAbilities(game)) {
|
||||
if (ability instanceof ReplicateAbility) {
|
||||
if (ability.isActivated()) {
|
||||
if ((ability instanceof ReplicateAbility) && ability.isActivated()) {
|
||||
((ReplicateAbility) ability).resetReplicate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// create the copies
|
||||
spell.createCopyOnStack(game, source, source.getControllerId(), true, replicateCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReplicateCopyEffect copy() {
|
||||
return new ReplicateCopyEffect(this);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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
Loading…
Reference in a new issue