mirror of
https://github.com/correl/mage.git
synced 2024-11-25 11:09:53 +00:00
[STX] add more cards (#7720)
* implement AcademicProbation * implement AugmenterPugilist // EchoingEquation * Implement BalefulMastery * implement BasicConjuration * implement ClosingStatement * Test framework: added custom effect to return card from any zone to hand; Co-authored-by: Oleg Agafonov <jaydi85@gmail.com>
This commit is contained in:
parent
ac2c2acfe4
commit
10cd439955
14 changed files with 766 additions and 57 deletions
96
Mage.Sets/src/mage/cards/a/AcademicProbation.java
Normal file
96
Mage.Sets/src/mage/cards/a/AcademicProbation.java
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.Mode;
|
||||||
|
import mage.abilities.effects.Effect;
|
||||||
|
import mage.abilities.effects.RestrictionEffect;
|
||||||
|
import mage.abilities.effects.common.OpponentsCantCastChosenUntilNextTurnEffect;
|
||||||
|
import mage.abilities.effects.common.ChooseACardNameEffect;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.Outcome;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.target.common.TargetNonlandPermanent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author htrajan
|
||||||
|
*/
|
||||||
|
public final class AcademicProbation extends CardImpl {
|
||||||
|
|
||||||
|
public AcademicProbation(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{W}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.LESSON);
|
||||||
|
|
||||||
|
// Choose one —
|
||||||
|
// • Choose a nonland card name. Opponents can't cast spells with the chosen name until your next turn.
|
||||||
|
Effect effect = new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.NON_LAND_NAME);
|
||||||
|
effect.setText("Choose a nonland card name");
|
||||||
|
this.getSpellAbility().addEffect(effect);
|
||||||
|
this.getSpellAbility().addEffect(new OpponentsCantCastChosenUntilNextTurnEffect().setText("opponents can't cast spells with the chosen name until your next turn"));
|
||||||
|
|
||||||
|
// • Choose target nonland permanent. Until your next turn, it can't attack or block, and its activated abilities can't be activated.
|
||||||
|
Mode restrictMode = new Mode();
|
||||||
|
restrictMode.addEffect(new AcademicProbationRestrictionEffect());
|
||||||
|
restrictMode.addTarget(new TargetNonlandPermanent());
|
||||||
|
this.getSpellAbility().addMode(restrictMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AcademicProbation(final AcademicProbation card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AcademicProbation copy() {
|
||||||
|
return new AcademicProbation(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AcademicProbationRestrictionEffect extends RestrictionEffect {
|
||||||
|
|
||||||
|
AcademicProbationRestrictionEffect() {
|
||||||
|
super(Duration.UntilYourNextTurn, Outcome.UnboostCreature);
|
||||||
|
staticText = "choose target nonland permanent. Until your next turn, it can't attack or block, and its activated abilities can't be activated";
|
||||||
|
}
|
||||||
|
|
||||||
|
AcademicProbationRestrictionEffect(final AcademicProbationRestrictionEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AcademicProbationRestrictionEffect copy() {
|
||||||
|
return new AcademicProbationRestrictionEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean applies(Permanent permanent, Ability source, Game game) {
|
||||||
|
return this.targetPointer.getTargets(game, source).contains(permanent.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canAttack(Game game, boolean canUseChooseDialogs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canBlock(Permanent attacker, Permanent blocker, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canUseActivatedAbilities(Permanent permanent, Ability source, Game game, boolean canUseChooseDialogs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
110
Mage.Sets/src/mage/cards/a/AugmenterPugilist.java
Normal file
110
Mage.Sets/src/mage/cards/a/AugmenterPugilist.java
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
package mage.cards.a;
|
||||||
|
|
||||||
|
import mage.MageObject;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition;
|
||||||
|
import mage.abilities.decorator.ConditionalContinuousEffect;
|
||||||
|
import mage.abilities.effects.OneShotEffect;
|
||||||
|
import mage.abilities.effects.common.continuous.BoostSourceEffect;
|
||||||
|
import mage.abilities.hint.common.LandsYouControlHint;
|
||||||
|
import mage.abilities.keyword.TrampleAbility;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.cards.ModalDoubleFacesCard;
|
||||||
|
import mage.constants.*;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.target.common.TargetControlledCreaturePermanent;
|
||||||
|
import mage.util.functions.CopyApplier;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author htrajan
|
||||||
|
*/
|
||||||
|
public final class AugmenterPugilist extends ModalDoubleFacesCard {
|
||||||
|
|
||||||
|
public AugmenterPugilist(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo,
|
||||||
|
new CardType[]{CardType.CREATURE}, new SubType[]{SubType.TROLL, SubType.DRUID}, "{1}{G}{G}",
|
||||||
|
"Echoing Equation", new CardType[]{CardType.SORCERY}, new SubType[]{}, "{3}{U}{U}");
|
||||||
|
|
||||||
|
// 1.
|
||||||
|
// Augmenter Pugilist
|
||||||
|
// Creature — Troll Druid
|
||||||
|
this.getLeftHalfCard().setPT(3, 3);
|
||||||
|
|
||||||
|
// Trample
|
||||||
|
this.getLeftHalfCard().addAbility(TrampleAbility.getInstance());
|
||||||
|
|
||||||
|
// As long as you control eight or more lands, Augmenter Pugilist gets +5/+5.
|
||||||
|
this.getLeftHalfCard().addAbility(new SimpleStaticAbility(
|
||||||
|
Zone.BATTLEFIELD,
|
||||||
|
new ConditionalContinuousEffect(
|
||||||
|
new BoostSourceEffect(
|
||||||
|
5, 5, Duration.WhileOnBattlefield
|
||||||
|
),
|
||||||
|
new PermanentsOnTheBattlefieldCondition(
|
||||||
|
StaticFilters.FILTER_CONTROLLED_PERMANENT_LAND,
|
||||||
|
ComparisonType.MORE_THAN, 7
|
||||||
|
),
|
||||||
|
"as long as you control eight or more lands, {this} gets +5/+5"
|
||||||
|
)
|
||||||
|
).addHint(LandsYouControlHint.instance));
|
||||||
|
|
||||||
|
// 2.
|
||||||
|
// Echoing Equation
|
||||||
|
// Sorcery
|
||||||
|
|
||||||
|
// Choose target creature you control. Each other creature you control becomes a copy of it until end of turn, except those creatures aren’t legendary if the chosen creature is legendary.
|
||||||
|
this.getRightHalfCard().getSpellAbility().addEffect(new EchoingEquationEffect());
|
||||||
|
this.getRightHalfCard().getSpellAbility().addTarget(new TargetControlledCreaturePermanent());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private AugmenterPugilist(final AugmenterPugilist card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AugmenterPugilist copy() {
|
||||||
|
return new AugmenterPugilist(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EchoingEquationEffect extends OneShotEffect {
|
||||||
|
|
||||||
|
public EchoingEquationEffect() {
|
||||||
|
super(Outcome.Benefit);
|
||||||
|
staticText = "choose target creature you control. Each other creature you control becomes a copy of it until end of turn, except those creatures aren't legendary if the chosen creature is legendary";
|
||||||
|
}
|
||||||
|
|
||||||
|
EchoingEquationEffect(EchoingEquationEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EchoingEquationEffect copy() {
|
||||||
|
return new EchoingEquationEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
Permanent copyFrom = game.getPermanent(source.getFirstTarget());
|
||||||
|
if (copyFrom != null) {
|
||||||
|
game.getBattlefield().getAllActivePermanents(source.getControllerId()).stream()
|
||||||
|
.filter(permanent -> permanent.isCreature() && !permanent.getId().equals(copyFrom.getId()))
|
||||||
|
.forEach(copyTo -> game.copyPermanent(Duration.EndOfTurn, copyFrom, copyTo.getId(), source, new CopyApplier() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, MageObject blueprint, Ability source, UUID targetObjectId) {
|
||||||
|
blueprint.getSuperType().remove(SuperType.LEGENDARY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
87
Mage.Sets/src/mage/cards/b/BalefulMastery.java
Normal file
87
Mage.Sets/src/mage/cards/b/BalefulMastery.java
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.costs.AlternativeCostSourceAbility;
|
||||||
|
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
|
import mage.abilities.effects.OneShotEffect;
|
||||||
|
import mage.abilities.effects.common.ExileTargetEffect;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.Outcome;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.players.Player;
|
||||||
|
import mage.target.common.TargetCreatureOrPlaneswalker;
|
||||||
|
import mage.target.common.TargetOpponent;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author htrajan
|
||||||
|
*/
|
||||||
|
public final class BalefulMastery extends CardImpl {
|
||||||
|
|
||||||
|
public BalefulMastery(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{B}");
|
||||||
|
|
||||||
|
// You may pay {1}{B} rather than pay this spell's mana cost.
|
||||||
|
Ability costAbility = new AlternativeCostSourceAbility(new ManaCostsImpl<>("{1}{B}"));
|
||||||
|
this.addAbility(costAbility);
|
||||||
|
|
||||||
|
// If the {1}{B} cost was paid, an opponent draws a card.
|
||||||
|
this.getSpellAbility().addEffect(new BalefulMasteryAlternativeCostEffect(costAbility.getOriginalId()));
|
||||||
|
|
||||||
|
// Exile target creature or planeswalker.
|
||||||
|
this.getSpellAbility().addEffect(new ExileTargetEffect());
|
||||||
|
this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BalefulMastery(final BalefulMastery card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BalefulMastery copy() {
|
||||||
|
return new BalefulMastery(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BalefulMasteryAlternativeCostEffect extends OneShotEffect {
|
||||||
|
|
||||||
|
UUID alternativeCostOriginalID;
|
||||||
|
|
||||||
|
BalefulMasteryAlternativeCostEffect(UUID alternativeCostOriginalID) {
|
||||||
|
super(Outcome.Detriment);
|
||||||
|
staticText = "if the {1}{B} cost was paid, an opponent draws a card.<br>";
|
||||||
|
this.alternativeCostOriginalID = alternativeCostOriginalID;
|
||||||
|
}
|
||||||
|
|
||||||
|
BalefulMasteryAlternativeCostEffect(BalefulMasteryAlternativeCostEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
this.alternativeCostOriginalID = effect.alternativeCostOriginalID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BalefulMasteryAlternativeCostEffect copy() {
|
||||||
|
return new BalefulMasteryAlternativeCostEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
boolean wasActivated = AlternativeCostSourceAbility.getActivatedStatus(game, source, this.alternativeCostOriginalID, false);
|
||||||
|
if (!wasActivated) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Player player = game.getPlayer(source.getControllerId());
|
||||||
|
TargetOpponent targetOpponent = new TargetOpponent(true);
|
||||||
|
if (player.chooseTarget(Outcome.DrawCard, targetOpponent, source, game)) {
|
||||||
|
Player opponent = game.getPlayer(targetOpponent.getFirstTarget());
|
||||||
|
if (opponent != null) {
|
||||||
|
opponent.drawCards(1, source, game);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
41
Mage.Sets/src/mage/cards/b/BasicConjuration.java
Normal file
41
Mage.Sets/src/mage/cards/b/BasicConjuration.java
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package mage.cards.b;
|
||||||
|
|
||||||
|
import mage.abilities.effects.common.GainLifeEffect;
|
||||||
|
import mage.abilities.effects.common.LookLibraryAndPickControllerEffect;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.CardType;
|
||||||
|
import mage.constants.SubType;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import mage.filter.StaticFilters;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author htrajan
|
||||||
|
*/
|
||||||
|
public final class BasicConjuration extends CardImpl {
|
||||||
|
|
||||||
|
public BasicConjuration(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}{G}");
|
||||||
|
|
||||||
|
this.subtype.add(SubType.LESSON);
|
||||||
|
|
||||||
|
// Look at the top six cards of your library. You may reveal a creature card from among them and put it into your hand. Put the rest on the bottom of your library in a random order. You gain 3 life.
|
||||||
|
this.getSpellAbility().addEffect(new LookLibraryAndPickControllerEffect(
|
||||||
|
6, 1, StaticFilters.FILTER_CARD_CREATURE_A,
|
||||||
|
true, false, Zone.HAND, true
|
||||||
|
).setBackInRandomOrder(true));
|
||||||
|
this.getSpellAbility().addEffect(new GainLifeEffect(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
private BasicConjuration(final BasicConjuration card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BasicConjuration copy() {
|
||||||
|
return new BasicConjuration(this);
|
||||||
|
}
|
||||||
|
}
|
97
Mage.Sets/src/mage/cards/c/ClosingStatement.java
Normal file
97
Mage.Sets/src/mage/cards/c/ClosingStatement.java
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
package mage.cards.c;
|
||||||
|
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.common.SimpleStaticAbility;
|
||||||
|
import mage.abilities.condition.common.IsPhaseCondition;
|
||||||
|
import mage.abilities.effects.OneShotEffect;
|
||||||
|
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||||
|
import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect;
|
||||||
|
import mage.abilities.hint.ConditionHint;
|
||||||
|
import mage.cards.CardImpl;
|
||||||
|
import mage.cards.CardSetInfo;
|
||||||
|
import mage.constants.*;
|
||||||
|
import mage.counters.CounterType;
|
||||||
|
import mage.filter.common.FilterCreatureOrPlaneswalkerPermanent;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.permanent.Permanent;
|
||||||
|
import mage.players.Player;
|
||||||
|
import mage.target.Target;
|
||||||
|
import mage.target.common.TargetControlledCreaturePermanent;
|
||||||
|
import mage.target.common.TargetCreatureOrPlaneswalker;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author htrajan
|
||||||
|
*/
|
||||||
|
public final class ClosingStatement extends CardImpl {
|
||||||
|
|
||||||
|
private static final FilterCreatureOrPlaneswalkerPermanent filter = new FilterCreatureOrPlaneswalkerPermanent("creature or planeswalker you don't control");
|
||||||
|
|
||||||
|
static {
|
||||||
|
filter.add(TargetController.NOT_YOU.getControllerPredicate());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClosingStatement(UUID ownerId, CardSetInfo setInfo) {
|
||||||
|
super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{W}{B}");
|
||||||
|
|
||||||
|
// This spell costs {2} less to cast during your end step.
|
||||||
|
IsPhaseCondition condition = new IsPhaseCondition(TurnPhase.END, true);
|
||||||
|
SimpleStaticAbility ability = new SimpleStaticAbility(Zone.ALL, new SpellCostReductionSourceEffect(2, condition).setText("this spell costs {2} less to cast during your end step"));
|
||||||
|
ability.addHint(new ConditionHint(condition, "On your end step"));
|
||||||
|
ability.setRuleAtTheTop(true);
|
||||||
|
this.addAbility(ability);
|
||||||
|
|
||||||
|
// Destroy target creature or planeswalker you don't control. Put a +1/+1 counter on up to one target creature you control.
|
||||||
|
this.getSpellAbility().addTarget(new TargetCreatureOrPlaneswalker(1, 1, filter, false));
|
||||||
|
this.getSpellAbility().addEffect(new DestroyTargetEffect());
|
||||||
|
Target target = new TargetControlledCreaturePermanent(0, 1);
|
||||||
|
target.setTargetTag(2);
|
||||||
|
this.getSpellAbility().addTarget(target);
|
||||||
|
this.getSpellAbility().addEffect(new ClosingStatementEffect());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClosingStatement(final ClosingStatement card) {
|
||||||
|
super(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClosingStatement copy() {
|
||||||
|
return new ClosingStatement(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClosingStatementEffect extends OneShotEffect {
|
||||||
|
|
||||||
|
ClosingStatementEffect() {
|
||||||
|
super(Outcome.Benefit);
|
||||||
|
staticText = "put a +1/+1 counter on up to one target creature you control";
|
||||||
|
}
|
||||||
|
|
||||||
|
private ClosingStatementEffect(final ClosingStatementEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClosingStatementEffect copy() {
|
||||||
|
return new ClosingStatementEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean apply(Game game, Ability source) {
|
||||||
|
Player player = game.getPlayer(source.getControllerId());
|
||||||
|
if (player == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Target target = source.getTargets().stream()
|
||||||
|
.filter(t -> t.getTargetTag() == 2)
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalStateException("Expected to find target with tag 2 but none exists"));
|
||||||
|
Permanent permanent = game.getPermanent(target.getFirstTarget());
|
||||||
|
if (permanent != null) {
|
||||||
|
return permanent.addCounters(CounterType.P1P1.createInstance(), source.getControllerId(), source, game);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,15 @@
|
||||||
package mage.cards.f;
|
package mage.cards.f;
|
||||||
|
|
||||||
import mage.MageObject;
|
|
||||||
import mage.abilities.Ability;
|
|
||||||
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
|
|
||||||
import mage.abilities.effects.Effect;
|
import mage.abilities.effects.Effect;
|
||||||
import mage.abilities.effects.common.ChooseACardNameEffect;
|
import mage.abilities.effects.common.ChooseACardNameEffect;
|
||||||
|
import mage.abilities.effects.common.OpponentsCantCastChosenUntilNextTurnEffect;
|
||||||
import mage.abilities.effects.common.ReturnToHandTargetEffect;
|
import mage.abilities.effects.common.ReturnToHandTargetEffect;
|
||||||
import mage.abilities.keyword.AftermathAbility;
|
import mage.abilities.keyword.AftermathAbility;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
import mage.cards.SplitCard;
|
import mage.cards.SplitCard;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
import mage.constants.Duration;
|
|
||||||
import mage.constants.Outcome;
|
|
||||||
import mage.constants.SpellAbilityType;
|
import mage.constants.SpellAbilityType;
|
||||||
import mage.game.Game;
|
|
||||||
import mage.game.events.GameEvent;
|
|
||||||
import mage.game.events.GameEvent.EventType;
|
|
||||||
import mage.target.TargetSpell;
|
import mage.target.TargetSpell;
|
||||||
import mage.util.CardUtil;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -41,7 +33,7 @@ public final class FailureComply extends SplitCard {
|
||||||
Effect effect = new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.ALL);
|
Effect effect = new ChooseACardNameEffect(ChooseACardNameEffect.TypeOfName.ALL);
|
||||||
effect.setText("Choose a card name");
|
effect.setText("Choose a card name");
|
||||||
getRightHalfCard().getSpellAbility().addEffect(effect);
|
getRightHalfCard().getSpellAbility().addEffect(effect);
|
||||||
getRightHalfCard().getSpellAbility().addEffect(new ComplyCantCastEffect());
|
getRightHalfCard().getSpellAbility().addEffect(new OpponentsCantCastChosenUntilNextTurnEffect());
|
||||||
}
|
}
|
||||||
|
|
||||||
private FailureComply(final FailureComply card) {
|
private FailureComply(final FailureComply card) {
|
||||||
|
@ -53,45 +45,3 @@ public final class FailureComply extends SplitCard {
|
||||||
return new FailureComply(this);
|
return new FailureComply(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ComplyCantCastEffect extends ContinuousRuleModifyingEffectImpl {
|
|
||||||
|
|
||||||
public ComplyCantCastEffect() {
|
|
||||||
super(Duration.UntilYourNextTurn, Outcome.Benefit);
|
|
||||||
staticText = "Until your next turn, your opponents can't cast spells with the chosen name";
|
|
||||||
}
|
|
||||||
|
|
||||||
public ComplyCantCastEffect(final ComplyCantCastEffect effect) {
|
|
||||||
super(effect);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ComplyCantCastEffect copy() {
|
|
||||||
return new ComplyCantCastEffect(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getInfoMessage(Ability source, GameEvent event, Game game) {
|
|
||||||
MageObject mageObject = game.getObject(source.getSourceId());
|
|
||||||
String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY);
|
|
||||||
if (mageObject != null && cardName != null) {
|
|
||||||
return "You can't cast a card named " + cardName + " (" + mageObject.getIdName() + ").";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean checksEventType(GameEvent event, Game game) {
|
|
||||||
return event.getType() == GameEvent.EventType.CAST_SPELL_LATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
|
||||||
String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY);
|
|
||||||
if (game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) {
|
|
||||||
MageObject object = game.getObject(event.getSourceId());
|
|
||||||
return object != null && CardUtil.haveSameNames(object, cardName, game);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ public final class StrixhavenSchoolOfMages extends ExpansionSet {
|
||||||
this.maxCardNumberInBooster = 275;
|
this.maxCardNumberInBooster = 275;
|
||||||
|
|
||||||
cards.add(new SetCardInfo("Academic Dispute", 91, Rarity.UNCOMMON, mage.cards.a.AcademicDispute.class));
|
cards.add(new SetCardInfo("Academic Dispute", 91, Rarity.UNCOMMON, mage.cards.a.AcademicDispute.class));
|
||||||
|
cards.add(new SetCardInfo("Academic Probation", 287, Rarity.RARE, mage.cards.a.AcademicProbation.class));
|
||||||
cards.add(new SetCardInfo("Access Tunnel", 262, Rarity.UNCOMMON, mage.cards.a.AccessTunnel.class));
|
cards.add(new SetCardInfo("Access Tunnel", 262, Rarity.UNCOMMON, mage.cards.a.AccessTunnel.class));
|
||||||
cards.add(new SetCardInfo("Accomplished Alchemist", 119, Rarity.RARE, mage.cards.a.AccomplishedAlchemist.class));
|
cards.add(new SetCardInfo("Accomplished Alchemist", 119, Rarity.RARE, mage.cards.a.AccomplishedAlchemist.class));
|
||||||
cards.add(new SetCardInfo("Aether Helix", 162, Rarity.UNCOMMON, mage.cards.a.AetherHelix.class));
|
cards.add(new SetCardInfo("Aether Helix", 162, Rarity.UNCOMMON, mage.cards.a.AetherHelix.class));
|
||||||
|
@ -42,6 +43,9 @@ public final class StrixhavenSchoolOfMages extends ExpansionSet {
|
||||||
cards.add(new SetCardInfo("Archway Commons", 263, Rarity.COMMON, mage.cards.a.ArchwayCommons.class));
|
cards.add(new SetCardInfo("Archway Commons", 263, Rarity.COMMON, mage.cards.a.ArchwayCommons.class));
|
||||||
cards.add(new SetCardInfo("Ardent Dustspeaker", 92, Rarity.UNCOMMON, mage.cards.a.ArdentDustspeaker.class));
|
cards.add(new SetCardInfo("Ardent Dustspeaker", 92, Rarity.UNCOMMON, mage.cards.a.ArdentDustspeaker.class));
|
||||||
cards.add(new SetCardInfo("Arrogant Poet", 63, Rarity.COMMON, mage.cards.a.ArrogantPoet.class));
|
cards.add(new SetCardInfo("Arrogant Poet", 63, Rarity.COMMON, mage.cards.a.ArrogantPoet.class));
|
||||||
|
cards.add(new SetCardInfo("Augmenter Pugilist", 147, Rarity.RARE, mage.cards.a.AugmenterPugilist.class));
|
||||||
|
cards.add(new SetCardInfo("Baleful Mastery", 64, Rarity.RARE, mage.cards.b.BalefulMastery.class));
|
||||||
|
cards.add(new SetCardInfo("Basic Conjuration", 120, Rarity.RARE, mage.cards.b.BasicConjuration.class));
|
||||||
cards.add(new SetCardInfo("Bayou Groff", 121, Rarity.COMMON, mage.cards.b.BayouGroff.class));
|
cards.add(new SetCardInfo("Bayou Groff", 121, Rarity.COMMON, mage.cards.b.BayouGroff.class));
|
||||||
cards.add(new SetCardInfo("Beaming Defiance", 9, Rarity.COMMON, mage.cards.b.BeamingDefiance.class));
|
cards.add(new SetCardInfo("Beaming Defiance", 9, Rarity.COMMON, mage.cards.b.BeamingDefiance.class));
|
||||||
cards.add(new SetCardInfo("Beledros Witherbloom", 163, Rarity.MYTHIC, mage.cards.b.BeledrosWitherbloom.class));
|
cards.add(new SetCardInfo("Beledros Witherbloom", 163, Rarity.MYTHIC, mage.cards.b.BeledrosWitherbloom.class));
|
||||||
|
@ -62,6 +66,7 @@ public final class StrixhavenSchoolOfMages extends ExpansionSet {
|
||||||
cards.add(new SetCardInfo("Campus Guide", 252, Rarity.COMMON, mage.cards.c.CampusGuide.class));
|
cards.add(new SetCardInfo("Campus Guide", 252, Rarity.COMMON, mage.cards.c.CampusGuide.class));
|
||||||
cards.add(new SetCardInfo("Charge Through", 124, Rarity.COMMON, mage.cards.c.ChargeThrough.class));
|
cards.add(new SetCardInfo("Charge Through", 124, Rarity.COMMON, mage.cards.c.ChargeThrough.class));
|
||||||
cards.add(new SetCardInfo("Clever Lumimancer", 10, Rarity.UNCOMMON, mage.cards.c.CleverLumimancer.class));
|
cards.add(new SetCardInfo("Clever Lumimancer", 10, Rarity.UNCOMMON, mage.cards.c.CleverLumimancer.class));
|
||||||
|
cards.add(new SetCardInfo("Closing Statement", 169, Rarity.UNCOMMON, mage.cards.c.ClosingStatement.class));
|
||||||
cards.add(new SetCardInfo("Cogwork Archivist", 254, Rarity.COMMON, mage.cards.c.CogworkArchivist.class));
|
cards.add(new SetCardInfo("Cogwork Archivist", 254, Rarity.COMMON, mage.cards.c.CogworkArchivist.class));
|
||||||
cards.add(new SetCardInfo("Combat Professor", 11, Rarity.COMMON, mage.cards.c.CombatProfessor.class));
|
cards.add(new SetCardInfo("Combat Professor", 11, Rarity.COMMON, mage.cards.c.CombatProfessor.class));
|
||||||
cards.add(new SetCardInfo("Confront the Past", 67, Rarity.RARE, mage.cards.c.ConfrontThePast.class));
|
cards.add(new SetCardInfo("Confront the Past", 67, Rarity.RARE, mage.cards.c.ConfrontThePast.class));
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
package org.mage.test.cards.single.stx;
|
||||||
|
|
||||||
|
import mage.constants.PhaseStep;
|
||||||
|
import mage.constants.Zone;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author htrajan, JayDi85
|
||||||
|
*/
|
||||||
|
public class BalefulMasteryTest extends CardTestPlayerBase {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_BalefulMastery_NormalCost() {
|
||||||
|
// You may pay {1}{B} rather than pay this spell's mana cost.
|
||||||
|
// If the {1}{B} cost was paid, an opponent draws a card.
|
||||||
|
// Exile target creature or planeswalker.
|
||||||
|
addCard(Zone.HAND, playerA, "Baleful Mastery"); // {3}{B}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4);
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Goblin Piker");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Witchbane Orb");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", "Goblin Piker");
|
||||||
|
setChoice(playerA, "No"); // use normal cost
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertHandCount(playerA, 0);
|
||||||
|
assertHandCount(playerB, 0);
|
||||||
|
assertExileCount(playerB, "Goblin Piker", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_BalefulMastery_AlternativeCost() {
|
||||||
|
// You may pay {1}{B} rather than pay this spell's mana cost.
|
||||||
|
// If the {1}{B} cost was paid, an opponent draws a card.
|
||||||
|
// Exile target creature or planeswalker.
|
||||||
|
addCard(Zone.HAND, playerA, "Baleful Mastery"); // {3}{B}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Goblin Piker");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Witchbane Orb");
|
||||||
|
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", "Goblin Piker");
|
||||||
|
setChoice(playerA, "Yes"); // use alternative cost
|
||||||
|
addTarget(playerA, playerB); // select opponent
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertHandCount(playerA, 0);
|
||||||
|
assertHandCount(playerB, 1); // +1 from cost's draw
|
||||||
|
assertExileCount(playerB, "Goblin Piker", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_BalefulMastery_DoubleCast() {
|
||||||
|
// You may pay {1}{B} rather than pay this spell's mana cost.
|
||||||
|
// If the {1}{B} cost was paid, an opponent draws a card.
|
||||||
|
// Exile target creature or planeswalker.
|
||||||
|
addCard(Zone.HAND, playerA, "Baleful Mastery", 2);
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2 + 4); // 1x normal, 1x alternative
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Goblin Piker");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears");
|
||||||
|
|
||||||
|
// cast 1 - alternative
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", "Goblin Piker");
|
||||||
|
setChoice(playerA, "Yes"); // use alternative cost
|
||||||
|
addTarget(playerA, playerB); // select opponent
|
||||||
|
|
||||||
|
// cast 2 - normal
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", "Grizzly Bears");
|
||||||
|
setChoice(playerA, "No"); // normal cast
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
|
||||||
|
assertHandCount(playerA, 0);
|
||||||
|
assertHandCount(playerB, 1);
|
||||||
|
assertExileCount(playerB, "Goblin Piker", 1);
|
||||||
|
assertExileCount(playerB, "Grizzly Bears", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_BalefulMastery_BlinkMustResetAlternativeCost() {
|
||||||
|
addCustomEffect_ReturnFromAnyToHand(playerA);
|
||||||
|
|
||||||
|
// You may pay {1}{B} rather than pay this spell's mana cost.
|
||||||
|
// If the {1}{B} cost was paid, an opponent draws a card.
|
||||||
|
// Exile target creature or planeswalker.
|
||||||
|
addCard(Zone.HAND, playerA, "Baleful Mastery"); // {3}{B}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2 + 4); // 1x normal, 1x alternative
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Goblin Piker");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears");
|
||||||
|
|
||||||
|
// cast 1 - with alternative
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", "Goblin Piker");
|
||||||
|
setChoice(playerA, "Yes"); // use alternative cost
|
||||||
|
addTarget(playerA, playerB); // select opponent
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkGraveyardCount("after cast 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", 1);
|
||||||
|
checkHandCount("after cast 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 0);
|
||||||
|
checkHandCount("after cast 1", 1, PhaseStep.PRECOMBAT_MAIN, playerB, 1); // +1 from cost's draw
|
||||||
|
checkExileCount("after cast 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Piker", 1);
|
||||||
|
checkExileCount("after cast 1", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 0);
|
||||||
|
|
||||||
|
// return to hand
|
||||||
|
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "return from graveyard");
|
||||||
|
addTarget(playerA, "Baleful Mastery");
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkHandCardCount("after return", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", 1);
|
||||||
|
|
||||||
|
// cast 2 - without alternative
|
||||||
|
// possible bug: cost status can be found from previous object (e.g. it ask about opponent select, but must not)
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", "Grizzly Bears");
|
||||||
|
setChoice(playerA, "No"); // do not use alternative cost
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||||
|
checkGraveyardCount("after cast 2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", 1);
|
||||||
|
checkHandCount("after cast 2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 0);
|
||||||
|
checkHandCount("after cast 2", 1, PhaseStep.PRECOMBAT_MAIN, playerB, 1); // no draws on cast 2
|
||||||
|
checkExileCount("after cast 2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Goblin Piker", 1);
|
||||||
|
checkExileCount("after cast 2", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Grizzly Bears", 1);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_BalefulMastery_CopyMustKeepAlternativeCost() {
|
||||||
|
// You may pay {1}{B} rather than pay this spell's mana cost.
|
||||||
|
// If the {1}{B} cost was paid, an opponent draws a card.
|
||||||
|
// Exile target creature or planeswalker.
|
||||||
|
addCard(Zone.HAND, playerA, "Baleful Mastery");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
|
||||||
|
//
|
||||||
|
// Copy target instant or sorcery spell. You may choose new targets for the copy.
|
||||||
|
addCard(Zone.HAND, playerA, "Twincast"); // {U}{U}
|
||||||
|
addCard(Zone.BATTLEFIELD, playerA, "Island", 2);
|
||||||
|
//
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Goblin Piker");
|
||||||
|
addCard(Zone.BATTLEFIELD, playerB, "Grizzly Bears");
|
||||||
|
|
||||||
|
// cast with alternative
|
||||||
|
activateManaAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Add {B}", 2);
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Baleful Mastery", "Goblin Piker");
|
||||||
|
setChoice(playerA, "Yes"); // use alternative cost
|
||||||
|
// copy spell
|
||||||
|
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Twincast", "Cast Baleful Mastery", "Cast Baleful Mastery");
|
||||||
|
setChoice(playerA, "Yes"); // change target
|
||||||
|
addTarget(playerA, "Grizzly Bears"); // new target
|
||||||
|
checkStackSize("before copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2);
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true);
|
||||||
|
checkStackSize("after copy", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 2);
|
||||||
|
//
|
||||||
|
// resolve copied spell
|
||||||
|
// possible bug: alternative cost will be lost for copied spell, so no opponent selections
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true);
|
||||||
|
addTarget(playerA, playerB); // select opponent
|
||||||
|
checkStackSize("after copy resolve", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 1);
|
||||||
|
// resolve original spell
|
||||||
|
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN, playerA, true);
|
||||||
|
addTarget(playerA, playerB); // select opponent
|
||||||
|
checkStackSize("after original resolve", 1, PhaseStep.PRECOMBAT_MAIN, playerA, 0);
|
||||||
|
|
||||||
|
setStrictChooseMode(true);
|
||||||
|
setStopAt(1, PhaseStep.END_TURN);
|
||||||
|
execute();
|
||||||
|
assertAllCommandsUsed();
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,8 +11,11 @@ import mage.abilities.costs.mana.ManaCostsImpl;
|
||||||
import mage.abilities.effects.Effect;
|
import mage.abilities.effects.Effect;
|
||||||
import mage.abilities.effects.common.DamageTargetEffect;
|
import mage.abilities.effects.common.DamageTargetEffect;
|
||||||
import mage.abilities.effects.common.DestroyTargetEffect;
|
import mage.abilities.effects.common.DestroyTargetEffect;
|
||||||
|
import mage.abilities.effects.common.ReturnFromExileEffect;
|
||||||
|
import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect;
|
||||||
import mage.abilities.effects.common.cost.SpellsCostIncreasingAllEffect;
|
import mage.abilities.effects.common.cost.SpellsCostIncreasingAllEffect;
|
||||||
import mage.abilities.effects.common.cost.SpellsCostReductionAllEffect;
|
import mage.abilities.effects.common.cost.SpellsCostReductionAllEffect;
|
||||||
|
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
|
||||||
import mage.cards.Card;
|
import mage.cards.Card;
|
||||||
import mage.cards.CardImpl;
|
import mage.cards.CardImpl;
|
||||||
import mage.cards.CardSetInfo;
|
import mage.cards.CardSetInfo;
|
||||||
|
@ -35,6 +38,9 @@ import mage.server.util.config.GamePlugin;
|
||||||
import mage.server.util.config.Plugin;
|
import mage.server.util.config.Plugin;
|
||||||
import mage.target.TargetPermanent;
|
import mage.target.TargetPermanent;
|
||||||
import mage.target.common.TargetAnyTarget;
|
import mage.target.common.TargetAnyTarget;
|
||||||
|
import mage.target.common.TargetCardInExile;
|
||||||
|
import mage.target.common.TargetCardInGraveyard;
|
||||||
|
import mage.target.common.TargetCardInLibrary;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
import mage.util.Copier;
|
import mage.util.Copier;
|
||||||
import org.apache.log4j.Level;
|
import org.apache.log4j.Level;
|
||||||
|
@ -484,6 +490,39 @@ public abstract class MageTestPlayerBase {
|
||||||
ability
|
ability
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return target card to hand that can be called by text "return from ..."
|
||||||
|
*
|
||||||
|
* @param controller
|
||||||
|
*/
|
||||||
|
protected void addCustomEffect_ReturnFromAnyToHand(TestPlayer controller) {
|
||||||
|
// graveyard
|
||||||
|
Ability ability = new SimpleActivatedAbility(new ReturnFromGraveyardToHandTargetEffect().setText("return from graveyard"), new ManaCostsImpl(""));
|
||||||
|
ability.addTarget(new TargetCardInGraveyard(StaticFilters.FILTER_CARD));
|
||||||
|
addCustomCardWithAbility(
|
||||||
|
"return from graveyard for " + controller.getName(),
|
||||||
|
controller,
|
||||||
|
ability
|
||||||
|
);
|
||||||
|
|
||||||
|
// exile
|
||||||
|
ability = new SimpleActivatedAbility(new ReturnFromExileEffect(Zone.HAND).setText("return from exile"), new ManaCostsImpl(""));
|
||||||
|
ability.addTarget(new TargetCardInExile(StaticFilters.FILTER_CARD));
|
||||||
|
addCustomCardWithAbility(
|
||||||
|
"return from exile for " + controller.getName(),
|
||||||
|
controller,
|
||||||
|
ability
|
||||||
|
);
|
||||||
|
|
||||||
|
// library
|
||||||
|
ability = new SimpleActivatedAbility(new SearchLibraryPutInHandEffect(new TargetCardInLibrary(StaticFilters.FILTER_CARD)).setText("return from library"), new ManaCostsImpl(""));
|
||||||
|
addCustomCardWithAbility(
|
||||||
|
"return from library for " + controller.getName(),
|
||||||
|
controller,
|
||||||
|
ability
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom card with global abilities list to init (can contains abilities per card name)
|
// custom card with global abilities list to init (can contains abilities per card name)
|
||||||
|
|
|
@ -586,6 +586,7 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement
|
||||||
*
|
*
|
||||||
* @param player {@link Player} to remove all cards from hand.
|
* @param player {@link Player} to remove all cards from hand.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated // TODO: remove, cause test games don't use starting draws
|
||||||
public void removeAllCardsFromHand(TestPlayer player) {
|
public void removeAllCardsFromHand(TestPlayer player) {
|
||||||
getCommands(player).put(Zone.HAND, "clear");
|
getCommands(player).put(Zone.HAND, "clear");
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,19 +15,29 @@ import java.util.Locale;
|
||||||
public class IsPhaseCondition implements Condition {
|
public class IsPhaseCondition implements Condition {
|
||||||
|
|
||||||
protected TurnPhase turnPhase;
|
protected TurnPhase turnPhase;
|
||||||
|
protected boolean yourTurn;
|
||||||
|
|
||||||
public IsPhaseCondition(TurnPhase turnPhase) {
|
public IsPhaseCondition(TurnPhase turnPhase) {
|
||||||
|
this(turnPhase, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IsPhaseCondition(TurnPhase turnPhase, boolean yourTurn) {
|
||||||
this.turnPhase = turnPhase;
|
this.turnPhase = turnPhase;
|
||||||
|
this.yourTurn = yourTurn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean apply(Game game, Ability source) {
|
public boolean apply(Game game, Ability source) {
|
||||||
return turnPhase == game.getTurn().getPhaseType();
|
return turnPhase == game.getTurn().getPhaseType() && (!yourTurn || game.getActivePlayerId().equals(source.getControllerId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new StringBuilder("during ").append(turnPhase).toString().toLowerCase(Locale.ENGLISH);
|
return new StringBuilder("during ")
|
||||||
|
.append(yourTurn ? "your " : "")
|
||||||
|
.append(turnPhase)
|
||||||
|
.toString()
|
||||||
|
.toLowerCase(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
package mage.abilities.costs;
|
package mage.abilities.costs;
|
||||||
|
|
||||||
import mage.abilities.Ability;
|
import mage.abilities.Ability;
|
||||||
|
@ -16,13 +15,15 @@ import mage.players.Player;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import mage.MageObject;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public class AlternativeCostSourceAbility extends StaticAbility implements AlternativeSourceCosts {
|
public class AlternativeCostSourceAbility extends StaticAbility implements AlternativeSourceCosts {
|
||||||
|
|
||||||
|
private static final String ALTERNATIVE_COST_ACTIVATION_KEY = "AlternativeCostActivated";
|
||||||
|
|
||||||
private Costs<AlternativeCost2> alternateCosts = new CostsImpl<>();
|
private Costs<AlternativeCost2> alternateCosts = new CostsImpl<>();
|
||||||
protected Condition condition;
|
protected Condition condition;
|
||||||
protected String rule;
|
protected String rule;
|
||||||
|
@ -159,6 +160,9 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// save activated status
|
||||||
|
game.getState().setValue(getActivatedKey(ability), Boolean.TRUE);
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -169,6 +173,38 @@ public class AlternativeCostSourceAbility extends StaticAbility implements Alter
|
||||||
return isActivated(ability, game);
|
return isActivated(ability, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getActivatedKey(Ability source) {
|
||||||
|
return getActivatedKey(this.getOriginalId(), source.getSourceId(), source.getSourceObjectZoneChangeCounter());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getActivatedKey(UUID alternativeCostOriginalId, UUID sourceId, int sourceZCC) {
|
||||||
|
// can't use sourceId cause copied cards are different...
|
||||||
|
// TODO: enable sourceId after copy card fix (it must copy cards with all related game state values)
|
||||||
|
return ALTERNATIVE_COST_ACTIVATION_KEY + "_" + alternativeCostOriginalId + "_" /*+ sourceId + "_"*/ + sourceZCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search activated status of alternative cost.
|
||||||
|
* <p>
|
||||||
|
* If you need it on resolve then use current ZCC (on stack)
|
||||||
|
* If you need it on battlefield then use previous ZCC (-1)
|
||||||
|
*
|
||||||
|
* @param game
|
||||||
|
* @param source
|
||||||
|
* @param alternativeCostOriginalId you must save originalId on card's creation
|
||||||
|
* @param searchPrevZCC true on battlefield, false on stack
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean getActivatedStatus(Game game, Ability source, UUID alternativeCostOriginalId, boolean searchPrevZCC) {
|
||||||
|
String key = getActivatedKey(
|
||||||
|
alternativeCostOriginalId,
|
||||||
|
source.getSourceId(),
|
||||||
|
source.getSourceObjectZoneChangeCounter() + (searchPrevZCC ? -1 : 0)
|
||||||
|
);
|
||||||
|
Boolean status = (Boolean) game.getState().getValue(key);
|
||||||
|
return status != null && status;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isActivated(Ability source, Game game) {
|
public boolean isActivated(Ability source, Game game) {
|
||||||
Costs<AlternativeCost2> alternativeCostsToCheck;
|
Costs<AlternativeCost2> alternativeCostsToCheck;
|
||||||
|
|
|
@ -78,7 +78,7 @@ public class CopyEffect extends ContinuousEffectImpl {
|
||||||
Permanent permanent = affectedObjectList.get(0).getPermanent(game);
|
Permanent permanent = affectedObjectList.get(0).getPermanent(game);
|
||||||
if (permanent == null) {
|
if (permanent == null) {
|
||||||
permanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, source.getSourceObjectZoneChangeCounter());
|
permanent = (Permanent) game.getLastKnownInformation(getSourceId(), Zone.BATTLEFIELD, source.getSourceObjectZoneChangeCounter());
|
||||||
// As long as the permanent is still in the LKI continue to copy to get triggered abilities to TriggeredAbilites for dies events.
|
// As long as the permanent is still in the LKI continue to copy to get triggered abilities to TriggeredAbilities for dies events.
|
||||||
if (permanent == null) {
|
if (permanent == null) {
|
||||||
discard();
|
discard();
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
package mage.abilities.effects.common;
|
||||||
|
|
||||||
|
import mage.MageObject;
|
||||||
|
import mage.abilities.Ability;
|
||||||
|
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
|
||||||
|
import mage.constants.Duration;
|
||||||
|
import mage.constants.Outcome;
|
||||||
|
import mage.game.Game;
|
||||||
|
import mage.game.events.GameEvent;
|
||||||
|
import mage.util.CardUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This effect must be used in tandem with ChooseACardNameEffect
|
||||||
|
*/
|
||||||
|
public class OpponentsCantCastChosenUntilNextTurnEffect extends ContinuousRuleModifyingEffectImpl {
|
||||||
|
|
||||||
|
public OpponentsCantCastChosenUntilNextTurnEffect() {
|
||||||
|
super(Duration.UntilYourNextTurn, Outcome.Benefit);
|
||||||
|
staticText = "Until your next turn, your opponents can't cast spells with the chosen name";
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpponentsCantCastChosenUntilNextTurnEffect(final OpponentsCantCastChosenUntilNextTurnEffect effect) {
|
||||||
|
super(effect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OpponentsCantCastChosenUntilNextTurnEffect copy() {
|
||||||
|
return new OpponentsCantCastChosenUntilNextTurnEffect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getInfoMessage(Ability source, GameEvent event, Game game) {
|
||||||
|
MageObject mageObject = game.getObject(source.getSourceId());
|
||||||
|
String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY);
|
||||||
|
if (mageObject != null && cardName != null) {
|
||||||
|
return "You can't cast a card named " + cardName + " (" + mageObject.getIdName() + ").";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean checksEventType(GameEvent event, Game game) {
|
||||||
|
return event.getType() == GameEvent.EventType.CAST_SPELL_LATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||||
|
String cardName = (String) game.getState().getValue(source.getSourceId().toString() + ChooseACardNameEffect.INFO_KEY);
|
||||||
|
if (game.getOpponents(source.getControllerId()).contains(event.getPlayerId())) {
|
||||||
|
MageObject object = game.getObject(event.getSourceId());
|
||||||
|
return object != null && CardUtil.haveSameNames(object, cardName, game);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue