PlayTheTopCard improves: added correct usage check, fixed outdated rule texts, Bolas's Citadel simplified (related to #7605);

This commit is contained in:
Oleg Agafonov 2021-02-21 03:53:33 +04:00
parent bfe91ad32b
commit 48e9cc3e07
27 changed files with 182 additions and 182 deletions

View file

@ -11,16 +11,16 @@ import mage.abilities.costs.common.TapSourceCost;
import mage.abilities.effects.AsThoughEffectImpl;
import mage.abilities.effects.common.LoseLifeOpponentsEffect;
import mage.abilities.effects.common.continuous.LookAtTopCardOfLibraryAnyTimeEffect;
import mage.cards.*;
import mage.cards.Card;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
import mage.constants.*;
import mage.filter.common.FilterControlledPermanent;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.players.Player;
import mage.target.common.TargetControlledPermanent;
import mage.util.CardUtil;
import java.util.Objects;
import java.util.UUID;
@ -89,69 +89,37 @@ class BolassCitadelPlayTheTopCardEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
return applies(objectId, null, source, game, affectedControllerId);
}
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
if (!Objects.equals(source.getControllerId(), playerId)) {
// current card's part
Card cardToCheck = game.getCard(objectId);
if (cardToCheck == null) {
return false;
}
Player player = game.getPlayer(playerId);
if (player != null) {
Card topCard = player.getLibrary().getFromTop(game);
UUID objectIdToCast = CardUtil.getMainCardId(game, objectId); // for adventure cards
if (topCard == null || !topCard.getId().equals(objectIdToCast)) {
return false;
}
if (topCard instanceof SplitCard || topCard instanceof ModalDoubleFacesCard) {
// double faces cards
Card card1;
Card card2;
if (topCard instanceof SplitCard) {
card1 = ((SplitCard) topCard).getLeftHalfCard();
card2 = ((SplitCard) topCard).getRightHalfCard();
} else {
card1 = ((ModalDoubleFacesCard) topCard).getLeftHalfCard();
card2 = ((ModalDoubleFacesCard) topCard).getRightHalfCard();
}
// left
if (!card1.isLand()) {
PayLifeCost lifeCost = new PayLifeCost(card1.getSpellAbility().getManaCosts().convertedManaCost());
Costs newCosts = new CostsImpl();
newCosts.add(lifeCost);
newCosts.addAll(card1.getSpellAbility().getCosts());
player.setCastSourceIdWithAlternateMana(card1.getId(), null, newCosts);
}
// right
if (!card2.isLand()) {
PayLifeCost lifeCost = new PayLifeCost(card2.getSpellAbility().getManaCosts().convertedManaCost());
Costs newCosts = new CostsImpl();
newCosts.add(lifeCost);
newCosts.addAll(card2.getSpellAbility().getCosts());
player.setCastSourceIdWithAlternateMana(card2.getId(), null, newCosts);
}
} else {
// other single face cards
if (!topCard.isLand()) {
if (affectedAbility == null) {
affectedAbility = topCard.getSpellAbility();
} else {
objectIdToCast = affectedAbility.getSourceId();
}
PayLifeCost cost = new PayLifeCost(affectedAbility.getManaCosts().convertedManaCost());
Costs costs = new CostsImpl();
costs.add(cost);
costs.addAll(affectedAbility.getCosts());
player.setCastSourceIdWithAlternateMana(objectIdToCast, null, costs);
}
}
return true;
// must be you
if (!affectedControllerId.equals(source.getControllerId())) {
return false;
}
return false;
// must be your card
Player player = game.getPlayer(cardToCheck.getOwnerId());
if (player == null) {
return false;
}
// must be from your library
Card topCard = player.getLibrary().getFromTop(game);
if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) {
return false;
}
// allows to play/cast with alternative life cost
if (!cardToCheck.isLand()) {
PayLifeCost lifeCost = new PayLifeCost(cardToCheck.getSpellAbility().getManaCosts().convertedManaCost());
Costs newCosts = new CostsImpl();
newCosts.add(lifeCost);
newCosts.addAll(cardToCheck.getSpellAbility().getCosts());
player.setCastSourceIdWithAlternateMana(cardToCheck.getId(), null, newCosts);
}
return true;
}
}

View file

@ -14,7 +14,6 @@ import mage.filter.FilterCard;
import java.util.UUID;
/**
*
* @author htrajan
*/
public final class ConspicuousSnoop extends CardImpl {
@ -27,7 +26,7 @@ public final class ConspicuousSnoop extends CardImpl {
public ConspicuousSnoop(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}{R}");
this.subtype.add(SubType.GOBLIN);
this.subtype.add(SubType.ROGUE);
this.power = new MageInt(2);
@ -37,7 +36,7 @@ public final class ConspicuousSnoop extends CardImpl {
this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect()));
// You may cast Goblin spells from the top of your library.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter)));
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)));
// As long as the top card of your library is a Goblin card, Conspicuous Snoop has all activated abilities of that card.
this.addAbility(new SimpleStaticAbility(new GainActivatedAbilitiesOfTopCardEffect(filter.copy().withMessage("a Goblin card"))));

View file

@ -21,7 +21,7 @@ import java.util.UUID;
*/
public final class CourserOfKruphix extends CardImpl {
private static final FilterCard filter = new FilterLandCard("play land cards");
private static final FilterCard filter = new FilterLandCard("play lands");
public CourserOfKruphix(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{1}{G}{G}");
@ -33,8 +33,8 @@ public final class CourserOfKruphix extends CardImpl {
// Play with the top card of your library revealed.
this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect()));
// You may play the top card of your library if it's a land card.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter)));
// You may play lands from the top of your library.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)));
// Whenever a land enters the battlefield under your control, you gain 1 life.
this.addAbility(new EntersBattlefieldControlledTriggeredAbility(new GainLifeEffect(1), StaticFilters.FILTER_LAND_A));

View file

@ -17,7 +17,7 @@ public final class CrucibleOfWorlds extends CardImpl {
public CrucibleOfWorlds(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}");
// You may play land cards from your graveyard.
// You may play lands from your graveyard.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayLandsFromGraveyardControllerEffect()));
}

View file

@ -45,8 +45,8 @@ public final class ElshaOfTheInfinite extends CardImpl {
// You may look at the top card of your library any time.
this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect()));
// You may cast the top card of your library if it's a noncreature, nonland card, and you may cast it as though it had flash.
Ability ability = new SimpleStaticAbility(new PlayTheTopCardEffect(filter));
// You may cast noncreature spells from the top of your library. If you cast a spell this way, you may cast it as though it had flash.
Ability ability = new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false));
ability.addEffect(new CastAsThoughItHadFlashAllEffect(
Duration.WhileOnBattlefield, filter
).setText("If you cast a spell this way, you may cast it as though it had flash."));

View file

@ -30,7 +30,7 @@ public final class ExperimentalFrenzy extends CardImpl {
// You may look at the top card of your library any time.
this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect()));
// You may play the top card of your library.
// You may play lands and cast spells from the top of your library.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect()));
// You can't play cards from your hand.

View file

@ -1,7 +1,5 @@
package mage.cards.f;
import java.util.UUID;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.PlayTheTopCardEffect;
import mage.abilities.effects.common.continuous.PlayWithTheTopCardRevealedEffect;
@ -10,18 +8,20 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.Zone;
import java.util.UUID;
/**
*
* @author Plopman
*/
public final class FutureSight extends CardImpl {
public FutureSight(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.ENCHANTMENT},"{2}{U}{U}{U}");
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{U}{U}");
// Play with the top card of your library revealed.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayWithTheTopCardRevealedEffect()));
// You may play the top card of your library.
// You may play lands and cast spells from the top of your library.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect()));
}

View file

@ -34,8 +34,8 @@ public final class GarruksHorde extends CardImpl {
// Play with the top card of your library revealed.
this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect()));
// You may cast the top card of your library if it's a creature card.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter)));
// You may cast creature spells from the top of your library.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)));
}
private GarruksHorde(final GarruksHorde card) {

View file

@ -1,7 +1,5 @@
package mage.cards.m;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.effects.common.continuous.PlayTheTopCardEffect;
@ -12,14 +10,15 @@ import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import java.util.UUID;
/**
*
* @author emerald000
*/
public final class MagusOfTheFuture extends CardImpl {
public MagusOfTheFuture(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{U}{U}{U}");
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}{U}{U}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.WIZARD);
@ -29,7 +28,7 @@ public final class MagusOfTheFuture extends CardImpl {
// Play with the top card of your library revealed.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayWithTheTopCardRevealedEffect()));
// You may play the top card of your library.
// You may play lands and cast spells from the top of your library.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect()));
}

View file

@ -17,7 +17,6 @@ import mage.filter.FilterCard;
import mage.filter.predicate.Predicates;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.GameEvent.EventType;
import mage.game.stack.Spell;
import mage.target.targetpointer.FixedTarget;
@ -49,8 +48,8 @@ public final class MelekIzzetParagon extends CardImpl {
// Play with the top card of your library revealed.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayWithTheTopCardRevealedEffect()));
// You may cast the top card of your library if it's an instant or sorcery card.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(filter)));
// You may cast instant and sorcery spells from the top of your library.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(filter, false)));
// Whenever you cast an instant or sorcery spell from your library, copy it. You may choose new targets for the copy.
this.addAbility(new MelekIzzetParagonTriggeredAbility());

View file

@ -1,9 +1,6 @@
package mage.cards.m;
import java.util.UUID;
import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.common.continuous.BoostAllEffect;
import mage.cards.CardImpl;
import mage.cards.CardSetInfo;
@ -14,9 +11,10 @@ import mage.constants.TargetController;
import mage.filter.StaticFilters;
import mage.filter.common.FilterLandPermanent;
import java.util.UUID;
/**
*
* @author North
*/
public final class Mutilate extends CardImpl {
@ -31,14 +29,15 @@ public final class Mutilate extends CardImpl {
}
public Mutilate(UUID ownerId, CardSetInfo setInfo) {
super(ownerId,setInfo,new CardType[]{CardType.SORCERY},"{2}{B}{B}");
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}{B}");
// All creatures get -1/-1 until end of turn for each Swamp you control.
PermanentsOnBattlefieldCount count = new PermanentsOnBattlefieldCount(filter, -1);
ContinuousEffect effect = new BoostAllEffect(count, count, Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES, false, null, true);
effect.overrideRuleText(ruleText);
this.getSpellAbility().addEffect(effect);
this.getSpellAbility().addEffect(
new BoostAllEffect(count, count, Duration.EndOfTurn, StaticFilters.FILTER_PERMANENT_CREATURES, false, null, true)
.setText(ruleText)
);
}
private Mutilate(final Mutilate card) {

View file

@ -43,7 +43,7 @@ public final class MysticForge extends CardImpl {
this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect()));
// You may cast artifact spells and colorless spells from the top of your library.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter)));
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)));
// {T}, Pay 1 life: Exile the top card of your library.
Ability ability = new SimpleActivatedAbility(new MysticForgeExileEffect(), new TapSourceCost());

View file

@ -20,7 +20,7 @@ import java.util.UUID;
*/
public final class OracleOfMulDaya extends CardImpl {
private static final FilterCard filter = new FilterLandCard("play land cards");
private static final FilterCard filter = new FilterLandCard("play lands");
public OracleOfMulDaya(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}");
@ -38,8 +38,8 @@ public final class OracleOfMulDaya extends CardImpl {
// Play with the top card of your library revealed.
this.addAbility(new SimpleStaticAbility(new PlayWithTheTopCardRevealedEffect()));
// You may play the top card of your library if it's a land card.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter)));
// You may play lands from the top of your library.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)));
}
private OracleOfMulDaya(final OracleOfMulDaya card) {

View file

@ -40,8 +40,8 @@ public final class PrecognitionField extends CardImpl {
// You may look at the top card of your library.
this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect()));
// You may cast the top card of your library if it's an instant or sorcery card.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter)));
// You may cast instant and sorcery spells from the top of your library.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)));
// {3}: Exile the top card of your library.
this.addAbility(new SimpleActivatedAbility(new PrecognitionFieldExileEffect(), new GenericManaCost(3)));

View file

@ -31,7 +31,7 @@ import java.util.UUID;
*/
public final class RadhaHeartOfKeld extends CardImpl {
private static final FilterCard filter = new FilterLandCard("play land cards");
private static final FilterCard filter = new FilterLandCard("play lands");
public RadhaHeartOfKeld(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{G}");
@ -50,9 +50,9 @@ public final class RadhaHeartOfKeld extends CardImpl {
// You may look at the top card of your library any time, and you may play lands from the top of your library.
LookAtTopCardOfLibraryAnyTimeEffect lookEffect = new LookAtTopCardOfLibraryAnyTimeEffect();
lookEffect.overrideRuleText("You may look at the top card of your library any time");
PlayTheTopCardEffect playEffect = new PlayTheTopCardEffect(filter);
playEffect.overrideRuleText(", and you may play lands from the top of your library");
lookEffect.setText("You may look at the top card of your library any time");
PlayTheTopCardEffect playEffect = new PlayTheTopCardEffect(filter, false);
playEffect.setText(", and you may play lands from the top of your library");
SimpleStaticAbility lookAndPlayAbility = new SimpleStaticAbility(lookEffect);
lookAndPlayAbility.addEffect(playEffect);
@ -61,7 +61,7 @@ public final class RadhaHeartOfKeld extends CardImpl {
// 4RG: Radha gets +X/+X until end of turn, where X is the number of lands you control.
DynamicValue controlledLands = new PermanentsOnBattlefieldCount(StaticFilters.FILTER_CONTROLLED_PERMANENT_LANDS);
BoostSourceEffect bse = new BoostSourceEffect(controlledLands, controlledLands, Duration.EndOfTurn, true);
bse.overrideRuleText("Radha gets +X/+X until end of turn, where X is the number of lands you control");
bse.setText("Radha gets +X/+X until end of turn, where X is the number of lands you control");
this.addAbility(new SimpleActivatedAbility(bse, new ManaCostsImpl("{4}{R}{G}")));
}

View file

@ -24,7 +24,7 @@ public final class RamunapExcavator extends CardImpl {
this.power = new MageInt(2);
this.toughness = new MageInt(3);
// You may play land cards from your graveyard.
// You may play lands from your graveyard.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayLandsFromGraveyardControllerEffect()));
}

View file

@ -1,6 +1,5 @@
package mage.cards.r;
import java.util.UUID;
import mage.MageInt;
import mage.abilities.common.AsEntersBattlefieldAbility;
import mage.abilities.common.SimpleStaticAbility;
@ -16,13 +15,15 @@ import mage.constants.SubType;
import mage.filter.common.FilterCreatureCard;
import mage.filter.predicate.mageobject.ChosenSubtypePredicate;
import java.util.UUID;
/**
*
* @author weirddan455
*/
public final class Realmwalker extends CardImpl {
private static final FilterCreatureCard filter = new FilterCreatureCard("cast creature spells of the chosen type");
static {
filter.add(ChosenSubtypePredicate.TRUE);
}
@ -44,7 +45,7 @@ public final class Realmwalker extends CardImpl {
this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect()));
// You may cast creature spells of the chosen type from the top of your library.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter)));
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)));
}
private Realmwalker(final Realmwalker card) {

View file

@ -11,21 +11,22 @@ import mage.cards.CardSetInfo;
import mage.constants.CardType;
import mage.constants.SubType;
import mage.constants.Zone;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.filter.common.FilterLandCard;
import mage.game.Game;
import java.util.Comparator;
import java.util.UUID;
/**
*
* @author htrajan
*/
public final class VergeRangers extends CardImpl {
public VergeRangers(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}");
this.subtype.add(SubType.HUMAN);
this.subtype.add(SubType.SCOUT);
this.power = new MageInt(3);
@ -53,9 +54,11 @@ public final class VergeRangers extends CardImpl {
class VergeRangersEffect extends PlayTheTopCardEffect {
private static final FilterCard filter = new FilterLandCard("play lands");
public VergeRangersEffect() {
super(StaticFilters.FILTER_CARD_LAND);
staticText = "As long as an opponent controls more lands than you, you may play lands from the top of your library.";
super(filter, false);
staticText = "As long as an opponent controls more lands than you, you may play lands from the top of your library";
}
public VergeRangersEffect(final VergeRangersEffect effect) {
@ -68,15 +71,16 @@ class VergeRangersEffect extends PlayTheTopCardEffect {
}
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
if (super.applies(objectId, affectedAbility, source, game, playerId)) {
int myLandCount = game.getBattlefield().countAll(StaticFilters.FILTER_LAND, playerId, game);
int maxOpponentLandCount = game.getOpponents(playerId).stream()
.map(opponentId -> game.getBattlefield().countAll(StaticFilters.FILTER_LAND, opponentId, game))
.max(Comparator.naturalOrder())
.orElse(0);
return maxOpponentLandCount > myLandCount;
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
if (!super.applies(objectId, source, affectedControllerId, game)) {
return false;
}
return false;
int myLandCount = game.getBattlefield().countAll(StaticFilters.FILTER_LAND, source.getControllerId(), game);
int maxOpponentLandCount = game.getOpponents(source.getControllerId()).stream()
.map(opponentId -> game.getBattlefield().countAll(StaticFilters.FILTER_LAND, opponentId, game))
.max(Comparator.naturalOrder())
.orElse(0);
return maxOpponentLandCount > myLandCount;
}
}

View file

@ -27,6 +27,7 @@ import mage.game.permanent.token.Token;
import mage.game.stack.Spell;
import mage.players.Player;
import mage.target.common.TargetCardInLibrary;
import java.util.Arrays;
import java.util.Locale;
import java.util.Set;
@ -51,7 +52,7 @@ public final class VivienMonstersAdvocate extends CardImpl {
this.addAbility(new SimpleStaticAbility(new LookAtTopCardOfLibraryAnyTimeEffect()));
// You may cast creature spells from the top of your library.
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter)));
this.addAbility(new SimpleStaticAbility(new PlayTheTopCardEffect(filter, false)));
// +1: Create a 3/3 green Beast creature token. Put your choice of a vigilance
// counter, a reach counter, or a trample counter on it.

View file

@ -36,8 +36,8 @@ public final class VizierOfTheMenagerie extends CardImpl {
// You may look at the top card of your library. (You may do this at any time.)
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new LookAtTopCardOfLibraryAnyTimeEffect()));
// You may cast the top card of your library if it's a creature card.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(filter)));
// You may cast creature spells from the top of your library.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new PlayTheTopCardEffect(filter, false)));
// You may spend mana as though it were mana of any type to cast creature spells.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new VizierOfTheMenagerieManaEffect()));

View file

@ -94,6 +94,7 @@ public class PlayTopCardFromLibraryTest extends CardTestPlayerBase {
addCard(Zone.BATTLEFIELD, playerA, "Bolas's Citadel", 1);
// Double your life total. Target opponent loses half their life, rounded up.
checkPlayableAbility("playable", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Revenge", true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Revenge", playerB); // {4}{W}{B} = 6 life
setStrictChooseMode(true);
@ -113,6 +114,7 @@ public class PlayTopCardFromLibraryTest extends CardTestPlayerBase {
addCard(Zone.GRAVEYARD, playerA, "Balduvian Bears", 1);
// Return target creature card with converted mana cost 3 or less from your graveyard to the battlefield.
checkPlayableAbility("playable", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Revival", true);
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Revival", "Balduvian Bears"); // {W/B}{W/B} = 2 life
setStrictChooseMode(true);

View file

@ -1,9 +1,5 @@
package mage.abilities.effects;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.constants.DependencyType;
@ -13,6 +9,11 @@ import mage.constants.SubLayer;
import mage.game.Game;
import mage.target.targetpointer.TargetPointer;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* @author BetaSteward_at_googlemail.com
*/
@ -46,8 +47,6 @@ public interface ContinuousEffect extends Effect {
SubLayer getSublayer();
void overrideRuleText(String text);
List<MageObjectReference> getAffectedObjects();
Set<UUID> isDependentTo(List<ContinuousEffect> allEffectsInLayer);

View file

@ -275,11 +275,6 @@ public abstract class ContinuousEffectImpl extends EffectImpl implements Continu
return sublayer;
}
@Override
public void overrideRuleText(String text) {
this.staticText = text;
}
protected static boolean isCanKill(DynamicValue toughness) {
if (toughness instanceof StaticValue) {
return toughness.calculate(null, null, null) < 0;

View file

@ -532,7 +532,10 @@ public class ContinuousEffects implements Serializable {
}
UUID idToCheck;
if (objectToCheck instanceof SplitCardHalf) {
if (!type.needPlayCardAbility() && objectToCheck instanceof SplitCardHalf) {
// each split side uses own characteristics to check for playing, all other cases must use main card
// rules:
// 708.4. In every zone except the stack, the characteristics of a split card are those of its two halves combined.
idToCheck = ((SplitCardHalf) objectToCheck).getMainCard().getId();
} else if (!type.needPlayCardAbility() && objectToCheck instanceof AdventureCardSpell) {
// adventure spell uses alternative characteristics for spell/stack, all other cases must use main card

View file

@ -7,34 +7,44 @@ import mage.constants.AsThoughEffectType;
import mage.constants.Duration;
import mage.constants.Outcome;
import mage.filter.FilterCard;
import mage.filter.StaticFilters;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.Locale;
import java.util.UUID;
/**
* @author nantuko
* @author nantuko, JayDi85
*/
public class PlayTheTopCardEffect extends AsThoughEffectImpl {
private final FilterCard filter;
// can play card or can play lands/cast spells, see two modes below
private final boolean canPlayCardOnly;
public PlayTheTopCardEffect() {
this(StaticFilters.FILTER_CARD);
staticText = "You may play lands and cast spells from the top of your library";
this(new FilterCard("play lands and cast spells"), false);
}
public PlayTheTopCardEffect(FilterCard filter) {
public PlayTheTopCardEffect(FilterCard filter, boolean canPlayCardOnly) {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
this.filter = filter;
staticText = "You may " + filter.getMessage() + " from the top of your library";
this.canPlayCardOnly = canPlayCardOnly;
this.staticText = "You may " + filter.getMessage() + " from the top of your library";
// verify check: if you see "card" text in the rules then use card mode
// (there aren't any real cards after oracle update, but can be added in the future)
if (this.canPlayCardOnly != filter.getMessage().toLowerCase(Locale.ENGLISH).contains("card")) {
throw new IllegalArgumentException("Wrong usage of card mode settings");
}
}
public PlayTheTopCardEffect(final PlayTheTopCardEffect effect) {
super(effect);
this.filter = effect.filter;
this.canPlayCardOnly = effect.canPlayCardOnly;
}
@Override
@ -49,25 +59,45 @@ public class PlayTheTopCardEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
return applies(objectId, null, source, game, affectedControllerId);
}
// main card and all parts are checks in different calls.
// two modes:
// * can play cards (must check main card and allows any parts)
// * can play lands/spells (must check specific part and allows specific part)
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
// current card's part
Card cardToCheck = game.getCard(objectId);
objectId = CardUtil.getMainCardId(game, objectId); // for split cards
if (cardToCheck != null
&& playerId.equals(source.getControllerId())
&& cardToCheck.isOwnedBy(source.getControllerId())
&& (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand())
&& filter.match(cardToCheck, source.getSourceId(), source.getControllerId(), game)) {
Player player = game.getPlayer(cardToCheck.getOwnerId());
UUID needCardID = player.getLibrary().getFromTop(game) == null ? null : player.getLibrary().getFromTop(game).getId();
return objectId.equals(needCardID);
if (cardToCheck == null) {
return false;
}
return false;
}
if (this.canPlayCardOnly) {
// check whole card intead part
cardToCheck = cardToCheck.getMainCard();
}
// must be you
if (!affectedControllerId.equals(source.getControllerId())) {
return false;
}
// must be your card
Player player = game.getPlayer(cardToCheck.getOwnerId());
if (player == null) {
return false;
}
// must be from your library
Card topCard = player.getLibrary().getFromTop(game);
if (topCard == null || !topCard.getId().equals(cardToCheck.getMainCard().getId())) {
return false;
}
// can't cast without mana cost
if (!cardToCheck.isLand() && cardToCheck.getManaCost().isEmpty()) {
return false;
}
// must be correct card
return filter.match(cardToCheck, source.getSourceId(), source.getControllerId(), game);
}
}

View file

@ -10,7 +10,6 @@ import mage.filter.FilterCard;
import mage.filter.common.FilterLandCard;
import mage.game.Game;
import mage.players.Player;
import mage.util.CardUtil;
import java.util.UUID;
@ -28,7 +27,7 @@ public class PlayLandsFromGraveyardControllerEffect extends AsThoughEffectImpl {
public PlayLandsFromGraveyardControllerEffect(FilterCard filter) {
super(AsThoughEffectType.PLAY_FROM_NOT_OWN_HAND_ZONE, Duration.WhileOnBattlefield, Outcome.Benefit);
this.filter = filter;
staticText = "You may play " + filter.getMessage() + " from your graveyard";
this.staticText = "You may play " + filter.getMessage() + " from your graveyard";
}
public PlayLandsFromGraveyardControllerEffect(final PlayLandsFromGraveyardControllerEffect effect) {
@ -49,30 +48,35 @@ public class PlayLandsFromGraveyardControllerEffect extends AsThoughEffectImpl {
@Override
public boolean applies(UUID objectId, Ability source, UUID affectedControllerId, Game game) {
return applies(objectId, null, source, game, affectedControllerId);
}
@Override
public boolean applies(UUID objectId, Ability affectedAbility, Ability source, Game game, UUID playerId) {
// current card's part
Card cardToCheck = game.getCard(objectId);
objectId = CardUtil.getMainCardId(game, objectId); // for split cards
if (cardToCheck == null) {
return false;
}
// must be you
if (!affectedControllerId.equals(source.getControllerId())) {
return false;
}
// must be your card
Player player = game.getPlayer(cardToCheck.getOwnerId());
if (player == null) {
return false;
}
UUID needCardId = objectId;
// must be from your graveyard
UUID needCardId = cardToCheck.getMainCard().getId();
if (player.getGraveyard().getCards(game).stream().noneMatch(c -> c.getId().equals(needCardId))) {
return false;
}
return playerId.equals(source.getControllerId())
&& cardToCheck.isOwnedBy(source.getControllerId())
&& (!cardToCheck.getManaCost().isEmpty() || cardToCheck.isLand())
&& filter.match(cardToCheck, game);
// can't cast without mana cost
if (!cardToCheck.isLand() && cardToCheck.getManaCost().isEmpty()) {
return false;
}
// must be correct card
return filter.match(cardToCheck, source.getSourceId(), source.getControllerId(), game);
}
}

View file

@ -1,5 +1,3 @@
package mage.abilities.keyword;
import mage.abilities.common.SpellCastControllerTriggeredAbility;
@ -10,20 +8,19 @@ import mage.filter.FilterSpell;
import mage.filter.predicate.Predicates;
/**
*
* @author LevelX2
*/
public class ProwessAbility extends SpellCastControllerTriggeredAbility {
private static final FilterSpell filterNonCreatureSpell = new FilterSpell("noncreature spell");
private static final FilterSpell filterNonCreatureSpell = new FilterSpell("noncreature spell");
static {
filterNonCreatureSpell.add(Predicates.not(CardType.CREATURE.getPredicate()));
}
public ProwessAbility() {
super(new BoostSourceEffect(1,1,Duration.EndOfTurn), false);
this.filter = filterNonCreatureSpell;
super(new BoostSourceEffect(1, 1, Duration.EndOfTurn), false);
this.filter = filterNonCreatureSpell;
}
public ProwessAbility(final ProwessAbility ability) {