forked from External/mage
fix Yasharn, Implacable Earth and Angel of Jubilation (#13753)
* Fix Angel of Jubilation and Yasharn, Implacable Earth * canPaySacrificeCost filter was not checking if the source ability was a spell or activated ability * Create common CantPayLifeOrSacrificeEffect * add some docs for CantPayLifeOrSacrificeEffect * change player pay life restrictions and remove player sacrifice cost filter * pay life cost restriction is now an enum set so multiple effects apply together * sacrifice cost filter was removed and replaced with PAY_SACRIFICE_COST event * convert CantPayLifeEffect to CantPayLifeOrSacrificeAbility * Changed to combine life restriction and sacrifice cost restriction * update bargain ability cost adjustors using canPay * fix Thran Portal * Effect was incorrectly adjusting the cost of mana abilities on itself. * Fixed ability adding type to itself during ETB * Add additional tests * update PayLifeCostRestrictions to be mutually exclusive
This commit is contained in:
parent
2833460e59
commit
574d7f91a5
18 changed files with 610 additions and 387 deletions
|
|
@ -1,23 +1,16 @@
|
|||
package mage.cards.a;
|
||||
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.CantPayLifeOrSacrificeAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.common.SacrificeTargetCost;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.common.continuous.BoostControlledEffect;
|
||||
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.filter.Filter;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.constants.SubType;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterCreaturePermanent;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -39,9 +32,7 @@ public final class AngelOfJubilation extends CardImpl {
|
|||
this.addAbility(new SimpleStaticAbility(new BoostControlledEffect(1, 1, Duration.WhileOnBattlefield, StaticFilters.FILTER_PERMANENT_CREATURES_NON_BLACK, true)));
|
||||
|
||||
// Players can't pay life or sacrifice creatures to cast spells or activate abilities.
|
||||
Ability ability = new SimpleStaticAbility(new AngelOfJubilationEffect());
|
||||
ability.addEffect(new AngelOfJubilationSacrificeFilterEffect());
|
||||
this.addAbility(ability);
|
||||
this.addAbility(new CantPayLifeOrSacrificeAbility(StaticFilters.FILTER_PERMANENT_CREATURES));
|
||||
}
|
||||
|
||||
private AngelOfJubilation(final AngelOfJubilation card) {
|
||||
|
|
@ -53,66 +44,3 @@ public final class AngelOfJubilation extends CardImpl {
|
|||
return new AngelOfJubilation(this);
|
||||
}
|
||||
}
|
||||
|
||||
class AngelOfJubilationEffect extends ContinuousEffectImpl {
|
||||
|
||||
AngelOfJubilationEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment);
|
||||
staticText = "Players can't pay life or sacrifice creatures to cast spells";
|
||||
}
|
||||
|
||||
private AngelOfJubilationEffect(final AngelOfJubilationEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AngelOfJubilationEffect copy() {
|
||||
return new AngelOfJubilationEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
player.setPayLifeCostLevel(Player.PayLifeCostLevel.nonSpellnonActivatedAbilities);
|
||||
player.setCanPaySacrificeCostFilter(new FilterCreaturePermanent());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class AngelOfJubilationSacrificeFilterEffect extends CostModificationEffectImpl {
|
||||
|
||||
AngelOfJubilationSacrificeFilterEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Detriment, CostModificationType.SET_COST);
|
||||
staticText = "or activate abilities";
|
||||
}
|
||||
|
||||
protected AngelOfJubilationSacrificeFilterEffect(AngelOfJubilationSacrificeFilterEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source, Ability abilityToModify) {
|
||||
for (Cost cost : abilityToModify.getCosts()) {
|
||||
if (cost instanceof SacrificeTargetCost) {
|
||||
SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost;
|
||||
Filter filter = sacrificeCost.getTargets().get(0).getFilter();
|
||||
filter.add(Predicates.not(CardType.CREATURE.getPredicate()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(Ability abilityToModify, Ability source, Game game) {
|
||||
return (abilityToModify.isActivatedAbility() || abilityToModify.getAbilityType() == AbilityType.SPELL)
|
||||
&& game.getState().getPlayersInRange(source.getControllerId(), game).contains(abilityToModify.getControllerId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AngelOfJubilationSacrificeFilterEffect copy() {
|
||||
return new AngelOfJubilationSacrificeFilterEffect(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,9 @@ import mage.cards.CardImpl;
|
|||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.Duration;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.game.Game;
|
||||
import mage.target.TargetPermanent;
|
||||
import mage.target.common.TargetControlledCreaturePermanent;
|
||||
import mage.target.common.TargetCreaturePermanent;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -65,7 +63,7 @@ enum BiteDownOnCrimeAdjuster implements CostAdjuster {
|
|||
@Override
|
||||
public void reduceCost(Ability ability, Game game) {
|
||||
if (CollectedEvidenceCondition.instance.apply(game, ability)
|
||||
|| (game.inCheckPlayableState() && collectEvidenceCost.canPay(ability, null, ability.getControllerId(), game))) {
|
||||
|| (game.inCheckPlayableState() && collectEvidenceCost.canPay(ability, ability, ability.getControllerId(), game))) {
|
||||
CardUtil.reduceCost(ability, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ enum HamletGluttonAdjuster implements CostAdjuster {
|
|||
@Override
|
||||
public void reduceCost(Ability ability, Game game) {
|
||||
if (BargainedCondition.instance.apply(game, ability)
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) {
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) {
|
||||
CardUtil.reduceCost(ability, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ enum IceOutAdjuster implements CostAdjuster {
|
|||
@Override
|
||||
public void reduceCost(Ability ability, Game game) {
|
||||
if (BargainedCondition.instance.apply(game, ability)
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) {
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) {
|
||||
CardUtil.reduceCost(ability, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ enum JohannsStopgapAdjuster implements CostAdjuster {
|
|||
@Override
|
||||
public void reduceCost(Ability ability, Game game) {
|
||||
if (BargainedCondition.instance.apply(game, ability)
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, null, ability.getControllerId(), game))) {
|
||||
|| (game.inCheckPlayableState() && bargainCost.canPay(ability, ability, ability.getControllerId(), game))) {
|
||||
CardUtil.reduceCost(ability, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,22 @@ package mage.cards.k;
|
|||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.ActivateAsSorceryActivatedAbility;
|
||||
import mage.abilities.common.CantPayLifeOrSacrificeAbility;
|
||||
import mage.abilities.common.EntersBattlefieldTappedAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.common.ExileSourceCost;
|
||||
import mage.abilities.costs.common.TapSourceCost;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
import mage.constants.*;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.ComparisonType;
|
||||
import mage.constants.Outcome;
|
||||
import mage.constants.SuperType;
|
||||
import mage.filter.common.FilterNonlandPermanent;
|
||||
import mage.filter.predicate.mageobject.ManaValuePredicate;
|
||||
import mage.game.Game;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
import mage.util.CardUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
|
@ -33,7 +34,7 @@ public class KarnsSylex extends CardImpl {
|
|||
this.addAbility(new EntersBattlefieldTappedAbility());
|
||||
|
||||
// Players can’t pay life to cast spells or to activate abilities that aren’t mana abilities.
|
||||
this.addAbility(new SimpleStaticAbility(new KarnsSylexEffect()));
|
||||
this.addAbility(new CantPayLifeOrSacrificeAbility(true, null));
|
||||
|
||||
// {X}, {T}, Exile Karn’s Sylex: Destroy each nonland permanent with mana value X or less. Activate only as a sorcery.
|
||||
Ability ability = new ActivateAsSorceryActivatedAbility(new KarnsSylexDestroyEffect(), new ManaCostsImpl<>("{X}"));
|
||||
|
|
@ -52,32 +53,6 @@ public class KarnsSylex extends CardImpl {
|
|||
}
|
||||
}
|
||||
|
||||
class KarnsSylexEffect extends ContinuousEffectImpl {
|
||||
|
||||
KarnsSylexEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment);
|
||||
staticText = "Players can't pay life to cast spells or to activate abilities that aren't mana abilities";
|
||||
}
|
||||
|
||||
private KarnsSylexEffect(final KarnsSylexEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KarnsSylexEffect copy() {
|
||||
return new KarnsSylexEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
player.setPayLifeCostLevel(Player.PayLifeCostLevel.onlyManaAbilities);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class KarnsSylexDestroyEffect extends OneShotEffect {
|
||||
|
||||
KarnsSylexDestroyEffect() {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ import mage.abilities.common.SimpleStaticAbility;
|
|||
import mage.abilities.condition.common.YouControlPermanentCondition;
|
||||
import mage.abilities.costs.common.PayLifeCost;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.OneShotEffect;
|
||||
import mage.abilities.effects.common.ChooseBasicLandTypeEffect;
|
||||
import mage.abilities.effects.common.continuous.AddChosenSubtypeEffect;
|
||||
import mage.abilities.effects.common.cost.CostModificationEffectImpl;
|
||||
import mage.abilities.effects.common.enterAttribute.EnterAttributeAddChosenSubtypeEffect;
|
||||
import mage.abilities.mana.*;
|
||||
import mage.cards.CardImpl;
|
||||
import mage.cards.CardSetInfo;
|
||||
|
|
@ -21,6 +19,7 @@ import mage.game.Game;
|
|||
import mage.game.permanent.Permanent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -47,16 +46,15 @@ public class ThranPortal extends CardImpl {
|
|||
this.addAbility(new EntersBattlefieldTappedUnlessAbility(condition).addHint(condition.getHint()));
|
||||
|
||||
// As Thran Portal enters the battlefield, choose a basic land type.
|
||||
// Thran Portal is the chosen type in addition to its other types.
|
||||
AsEntersBattlefieldAbility chooseLandTypeAbility = new AsEntersBattlefieldAbility(new ChooseBasicLandTypeEffect(Outcome.AddAbility));
|
||||
chooseLandTypeAbility.addEffect(new EnterAttributeAddChosenSubtypeEffect()); // While it enters
|
||||
chooseLandTypeAbility.addEffect(new ThranPortalAddSubtypeEnteringEffect());
|
||||
this.addAbility(chooseLandTypeAbility);
|
||||
this.addAbility(new SimpleStaticAbility(new AddChosenSubtypeEffect())); // While on the battlefield
|
||||
|
||||
// Thran Portal is the chosen type in addition to its other types.
|
||||
this.addAbility(new SimpleStaticAbility(new ThranPortalManaAbilityContinuousEffect()));
|
||||
|
||||
// Mana abilities of Thran Portal cost an additional 1 life to activate.
|
||||
// This also adds the mana ability
|
||||
Ability ability = new SimpleStaticAbility(new ThranPortalAdditionalCostEffect());
|
||||
ability.addEffect(new ThranPortalManaAbilityContinousEffect());
|
||||
this.addAbility(ability);
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +68,34 @@ public class ThranPortal extends CardImpl {
|
|||
}
|
||||
}
|
||||
|
||||
class ThranPortalManaAbilityContinousEffect extends ContinuousEffectImpl {
|
||||
class ThranPortalAddSubtypeEnteringEffect extends OneShotEffect {
|
||||
|
||||
public ThranPortalAddSubtypeEnteringEffect() {
|
||||
super(Outcome.Benefit);
|
||||
}
|
||||
|
||||
protected ThranPortalAddSubtypeEnteringEffect(final ThranPortalAddSubtypeEnteringEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThranPortalAddSubtypeEnteringEffect copy() {
|
||||
return new ThranPortalAddSubtypeEnteringEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent thranPortal = game.getPermanentEntering(source.getSourceId());
|
||||
SubType choice = SubType.byDescription((String) game.getState().getValue(source.getSourceId().toString() + ChooseBasicLandTypeEffect.VALUE_KEY));
|
||||
if (thranPortal != null && choice != null) {
|
||||
thranPortal.addSubType(choice);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class ThranPortalManaAbilityContinuousEffect extends ContinuousEffectImpl {
|
||||
|
||||
private static final Map<SubType, BasicManaAbility> abilityMap = new HashMap<SubType, BasicManaAbility>() {{
|
||||
put(SubType.PLAINS, new WhiteManaAbility());
|
||||
|
|
@ -80,18 +105,18 @@ class ThranPortalManaAbilityContinousEffect extends ContinuousEffectImpl {
|
|||
put(SubType.FOREST, new GreenManaAbility());
|
||||
}};
|
||||
|
||||
public ThranPortalManaAbilityContinousEffect() {
|
||||
public ThranPortalManaAbilityContinuousEffect() {
|
||||
super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral);
|
||||
staticText = "mana abilities of {this} cost an additional 1 life to activate";
|
||||
staticText = "{this} is the chosen type in addition to its other types.";
|
||||
}
|
||||
|
||||
private ThranPortalManaAbilityContinousEffect(final ThranPortalManaAbilityContinousEffect effect) {
|
||||
private ThranPortalManaAbilityContinuousEffect(final ThranPortalManaAbilityContinuousEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThranPortalManaAbilityContinousEffect copy() {
|
||||
return new ThranPortalManaAbilityContinousEffect(this);
|
||||
public ThranPortalManaAbilityContinuousEffect copy() {
|
||||
return new ThranPortalManaAbilityContinuousEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -143,10 +168,11 @@ class ThranPortalManaAbilityContinousEffect extends ContinuousEffectImpl {
|
|||
}
|
||||
}
|
||||
|
||||
class ThranPortalAdditionalCostEffect extends CostModificationEffectImpl {
|
||||
class ThranPortalAdditionalCostEffect extends ContinuousEffectImpl {
|
||||
|
||||
ThranPortalAdditionalCostEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Benefit, CostModificationType.INCREASE_COST);
|
||||
super(Duration.WhileOnBattlefield, Layer.RulesEffects, SubLayer.NA, Outcome.Benefit);
|
||||
staticText = "mana abilities of {this} cost an additional 1 life to activate";
|
||||
}
|
||||
|
||||
private ThranPortalAdditionalCostEffect(final ThranPortalAdditionalCostEffect effect) {
|
||||
|
|
@ -159,17 +185,22 @@ class ThranPortalAdditionalCostEffect extends CostModificationEffectImpl {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source, Ability abilityToModify) {
|
||||
abilityToModify.addCost(new PayLifeCost(1));
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(Ability abilityToModify, Ability source, Game game) {
|
||||
if (!abilityToModify.getSourceId().equals(source.getSourceId())) {
|
||||
public boolean apply(Game game, Ability source) {
|
||||
Permanent thranPortal = game.getPermanent(source.getSourceId());
|
||||
if (thranPortal == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return abilityToModify instanceof ManaAbility;
|
||||
List<Ability> abilities = thranPortal.getAbilities(game);
|
||||
if (abilities.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean result = false;
|
||||
for (Ability ability : abilities) {
|
||||
if (ability.isManaAbility()) {
|
||||
ability.addCost(new PayLifeCost(1));
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,22 @@
|
|||
package mage.cards.y;
|
||||
|
||||
import java.util.Optional;
|
||||
import mage.MageInt;
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.common.SimpleStaticAbility;
|
||||
import mage.abilities.costs.Cost;
|
||||
import mage.abilities.costs.common.SacrificeTargetCost;
|
||||
import mage.abilities.assignment.common.SubTypeAssignment;
|
||||
import mage.abilities.common.CantPayLifeOrSacrificeAbility;
|
||||
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
|
||||
import mage.abilities.effects.common.search.SearchLibraryPutInHandEffect;
|
||||
import mage.cards.*;
|
||||
import mage.constants.*;
|
||||
import mage.constants.CardType;
|
||||
import mage.constants.SubType;
|
||||
import mage.constants.SuperType;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.predicate.Predicates;
|
||||
import mage.game.Game;
|
||||
import mage.target.common.TargetCardInLibrary;
|
||||
|
||||
import java.util.UUID;
|
||||
import mage.MageObject;
|
||||
import mage.abilities.costs.common.PayLifeCost;
|
||||
import mage.abilities.costs.common.PayVariableLifeCost;
|
||||
import mage.abilities.costs.common.SacrificeAllCost;
|
||||
import mage.abilities.costs.common.SacrificeAttachedCost;
|
||||
import mage.abilities.costs.common.SacrificeAttachmentCost;
|
||||
import mage.abilities.costs.common.SacrificeSourceCost;
|
||||
import mage.abilities.costs.common.SacrificeXTargetCost;
|
||||
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
|
||||
import mage.filter.Filter;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
|
||||
/**
|
||||
* @author TheElk801
|
||||
|
|
@ -52,8 +40,7 @@ public final class YasharnImplacableEarth extends CardImpl {
|
|||
));
|
||||
|
||||
// Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities.
|
||||
Ability ability = new SimpleStaticAbility(new YasharnImplacableEarthEffect());
|
||||
this.addAbility(ability);
|
||||
this.addAbility(new CantPayLifeOrSacrificeAbility(StaticFilters.FILTER_PERMANENTS_NON_LAND));
|
||||
}
|
||||
|
||||
private YasharnImplacableEarth(final YasharnImplacableEarth card) {
|
||||
|
|
@ -111,122 +98,3 @@ class YasharnImplacableEarthTarget extends TargetCardInLibrary {
|
|||
return subTypeAssigner.getRoleCount(cards, game) >= cards.size();
|
||||
}
|
||||
}
|
||||
|
||||
class YasharnImplacableEarthEffect extends ContinuousRuleModifyingEffectImpl {
|
||||
|
||||
YasharnImplacableEarthEffect() {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Neutral);
|
||||
staticText = "Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities";
|
||||
}
|
||||
|
||||
private YasharnImplacableEarthEffect(final YasharnImplacableEarthEffect effect) {
|
||||
super(effect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public YasharnImplacableEarthEffect copy() {
|
||||
return new YasharnImplacableEarthEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInfoMessage(Ability source, GameEvent event, Game game) {
|
||||
MageObject mageObject = game.getObject(source);
|
||||
if (mageObject != null) {
|
||||
return "Players can't pay life or sacrifice nonland permanents to cast spells or activate abilities. (" + mageObject.getIdName() + ").";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checksEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.ACTIVATE_ABILITY
|
||||
|| event.getType() == GameEvent.EventType.CAST_SPELL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
Permanent permanent = game.getPermanentOrLKIBattlefield(event.getSourceId());
|
||||
if (event.getType() == GameEvent.EventType.ACTIVATE_ABILITY && permanent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean canTargetLand = true;
|
||||
Optional<Ability> ability = game.getAbility(event.getTargetId(), event.getSourceId());
|
||||
if (!ability.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Cost cost : ability.get().getCosts()) {
|
||||
if (cost instanceof PayLifeCost
|
||||
|| cost instanceof PayVariableLifeCost) {
|
||||
return true; // can't pay with life
|
||||
}
|
||||
if (cost instanceof SacrificeSourceCost
|
||||
&& !permanent.isLand(game)) {
|
||||
return true;
|
||||
}
|
||||
if (cost instanceof SacrificeTargetCost) {
|
||||
SacrificeTargetCost sacrificeCost = (SacrificeTargetCost) cost;
|
||||
Filter filter = sacrificeCost.getTargets().get(0).getFilter();
|
||||
for (Object predicate : filter.getPredicates()) {
|
||||
if (predicate instanceof CardType.CardTypePredicate) {
|
||||
if (!predicate.toString().equals("CardType(Land)")) {
|
||||
canTargetLand = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return !canTargetLand; // must be nonland target
|
||||
}
|
||||
if (cost instanceof SacrificeAllCost) {
|
||||
SacrificeAllCost sacrificeAllCost = (SacrificeAllCost) cost;
|
||||
Filter filter = sacrificeAllCost.getTargets().get(0).getFilter();
|
||||
for (Object predicate : filter.getPredicates()) {
|
||||
if (predicate instanceof CardType.CardTypePredicate) {
|
||||
if (!predicate.toString().equals("CardType(Land)")) {
|
||||
canTargetLand = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return !canTargetLand; // must be nonland target
|
||||
}
|
||||
if (cost instanceof SacrificeAttachedCost) {
|
||||
SacrificeAttachedCost sacrificeAllCost = (SacrificeAttachedCost) cost;
|
||||
Filter filter = sacrificeAllCost.getTargets().get(0).getFilter();
|
||||
for (Object predicate : filter.getPredicates()) {
|
||||
if (predicate instanceof CardType.CardTypePredicate) {
|
||||
if (!predicate.toString().equals("CardType(Land)")) {
|
||||
canTargetLand = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return !canTargetLand; // must be nonland target
|
||||
}
|
||||
if (cost instanceof SacrificeAttachmentCost) {
|
||||
SacrificeAttachmentCost sacrificeAllCost = (SacrificeAttachmentCost) cost;
|
||||
Filter filter = sacrificeAllCost.getTargets().get(0).getFilter();
|
||||
for (Object predicate : filter.getPredicates()) {
|
||||
if (predicate instanceof CardType.CardTypePredicate) {
|
||||
if (!predicate.toString().equals("CardType(Land)")) {
|
||||
canTargetLand = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return !canTargetLand; // must be nonland target
|
||||
}
|
||||
|
||||
if (cost instanceof SacrificeXTargetCost) {
|
||||
SacrificeXTargetCost sacrificeCost = (SacrificeXTargetCost) cost;
|
||||
Filter filter = sacrificeCost.getFilter();
|
||||
for (Object predicate : filter.getPredicates()) {
|
||||
if (predicate instanceof CardType.CardTypePredicate) {
|
||||
if (!predicate.toString().equals("CardType(Land)")) {
|
||||
canTargetLand = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return !canTargetLand; // must be nonland target
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -337,4 +337,20 @@ public class BargainTest extends CardTestPlayerBase {
|
|||
assertLife(playerA, 20 + 3);
|
||||
assertTappedCount("Forest", true, 7);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCantBargainWithRestriction() {
|
||||
setStrictChooseMode(true);
|
||||
// Players can’t pay life or sacrifice nonland permanents to cast spells or activate abilities.
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Yasharn, Implacable Earth");
|
||||
addCard(Zone.HAND, playerA, glutton);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, relic);
|
||||
|
||||
checkPlayableAbility("restricted by Yasharn", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Hamlet Glutton", false);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
package org.mage.test.cards.continuous;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.permanent.token.FoodToken;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
|
|
@ -18,12 +24,14 @@ import org.mage.test.serverside.base.CardTestPlayerBase;
|
|||
*/
|
||||
public class AngelOfJubilationTest extends CardTestPlayerBase {
|
||||
|
||||
public static final String angelOfJubilation = "Angel of Jubilation";
|
||||
|
||||
/**
|
||||
* Tests boosting other non black creatures
|
||||
*/
|
||||
@Test
|
||||
public void testBoost() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Devout Chaplain");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Corpse Traders");
|
||||
|
||||
|
|
@ -33,7 +41,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 20);
|
||||
|
||||
assertPowerToughness(playerA, "Angel of Jubilation", 3, 3);
|
||||
assertPowerToughness(playerA, angelOfJubilation, 3, 3);
|
||||
assertPowerToughness(playerA, "Devout Chaplain", 3, 3);
|
||||
assertPowerToughness(playerA, "Corpse Traders", 3, 3);
|
||||
}
|
||||
|
|
@ -43,14 +51,14 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
*/
|
||||
@Test
|
||||
public void testNoBoostOnBattlefieldLeave() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Devout Chaplain");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Corpse Traders");
|
||||
|
||||
addCard(Zone.HAND, playerA, "Lightning Bolt");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain");
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", "Angel of Jubilation");
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Lightning Bolt", angelOfJubilation);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
|
@ -58,14 +66,14 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 20);
|
||||
|
||||
assertPermanentCount(playerA, "Angel of Jubilation", 0);
|
||||
assertPermanentCount(playerA, angelOfJubilation, 0);
|
||||
assertPowerToughness(playerA, "Devout Chaplain", 2, 2);
|
||||
assertPowerToughness(playerA, "Corpse Traders", 3, 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpponentCantSacrificeCreatures() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Corpse Traders");
|
||||
|
||||
|
|
@ -81,7 +89,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
|
||||
@Test
|
||||
public void testOpponentCanSacrificeNonCreaturePermanents() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Savannah Lions");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Barrin, Master Wizard");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk");
|
||||
|
|
@ -89,20 +97,20 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
addCard(Zone.BATTLEFIELD, playerB, "Food Chain");
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{2}, Sacrifice a permanent: Return target creature to its owner's hand.");
|
||||
addTarget(playerB, "Angel of Jubilation"); // return to hand
|
||||
addTarget(playerB, angelOfJubilation); // return to hand
|
||||
setChoice(playerB, "Food Chain"); // sacrifice cost
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Angel of Jubilation", 0);
|
||||
assertPermanentCount(playerA, angelOfJubilation, 0);
|
||||
assertPermanentCount(playerB, "Food Chain", 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpponentCantSacrificeCreaturesAsPartOfPermanentsOptions() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Barrin, Master Wizard");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Llanowar Elves", 2);
|
||||
|
|
@ -115,13 +123,13 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Angel of Jubilation", 1);
|
||||
assertPermanentCount(playerA, angelOfJubilation, 1);
|
||||
assertPermanentCount(playerB, "Nantuko Husk", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpponentCantSacrificeAll() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Nantuko Husk");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Corpse Traders");
|
||||
addCard(Zone.HAND, playerB, "Soulblast");
|
||||
|
|
@ -142,7 +150,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
|
||||
@Test
|
||||
public void testOpponentCantSacrificeCreatureSource() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Children of Korlis");
|
||||
|
||||
checkPlayableAbility("Can't sac", 1, PhaseStep.PRECOMBAT_MAIN, playerB, "Sacrifice", false);
|
||||
|
|
@ -155,7 +163,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
|
||||
@Test
|
||||
public void testOpponentCanSacrificeAllLands() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Tomb of Urami");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 4);
|
||||
|
||||
|
|
@ -169,7 +177,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
|
||||
@Test
|
||||
public void testOpponentCanSacrificeNonCreatureSource() {
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Tundra");
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Wasteland");
|
||||
|
||||
|
|
@ -195,7 +203,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
setStrictChooseMode(true);
|
||||
// Other nonblack creatures you control get +1/+1.
|
||||
// Players can't pay life or sacrifice creatures to cast spells or activate abilities
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
|
||||
|
||||
// Indestructible
|
||||
|
|
@ -234,7 +242,7 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
setStrictChooseMode(true);
|
||||
// Other nonblack creatures you control get +1/+1.
|
||||
// Players can't pay life or sacrifice creatures to cast spells or activate abilities
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Angel of Jubilation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
|
||||
// Pay 7 life: Draw seven cards.
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Griselbrand");
|
||||
|
|
@ -244,4 +252,90 @@ public class AngelOfJubilationTest extends CardTestPlayerBase {
|
|||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanSacrificeTriggeredAbility() {
|
||||
/*
|
||||
Unscrupulous Contractor
|
||||
{2}{B}
|
||||
Creature — Human Assassin
|
||||
When this creature enters, you may sacrifice a creature. When you do, target player draws two cards and loses 2 life.
|
||||
Plot {2}{B}
|
||||
3/2
|
||||
*/
|
||||
String contractor = "Unscrupulous Contractor";
|
||||
/*
|
||||
Bear Cub
|
||||
{1}{G}
|
||||
Creature - Bear
|
||||
2/2
|
||||
*/
|
||||
String cub = "Bear Cub";
|
||||
|
||||
setStrictChooseMode(true);
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.HAND, playerA, contractor);
|
||||
addCard(Zone.HAND, playerB, contractor);
|
||||
addCard(Zone.BATTLEFIELD, playerA, cub);
|
||||
addCard(Zone.BATTLEFIELD, playerB, cub);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, contractor);
|
||||
setChoice(playerA, true);
|
||||
setChoice(playerA, cub);
|
||||
addTarget(playerA, playerA);
|
||||
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, contractor);
|
||||
setChoice(playerB, true);
|
||||
setChoice(playerB, cub);
|
||||
addTarget(playerB, playerB);
|
||||
|
||||
setStopAt(2, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertHandCount(playerA, 2);
|
||||
assertLife(playerA, 20 - 2);
|
||||
assertGraveyardCount(playerA, cub, 1);
|
||||
|
||||
assertHandCount(playerB, 1 + 2); //draw + contractor effect
|
||||
assertLife(playerB, 20 - 2);
|
||||
assertGraveyardCount(playerB, cub, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canSacToMondrakWithArtifacts() {
|
||||
setStrictChooseMode(true);
|
||||
//Mondrak, Glory Dominus
|
||||
//{2}{W}{W}
|
||||
//Legendary Creature — Phyrexian Horror
|
||||
//If one or more tokens would be created under your control, twice that many of those tokens are created instead.
|
||||
//{1}{W/P}{W/P}, Sacrifice two other artifacts and/or creatures: Put an indestructible counter on Mondrak.
|
||||
String mondrak = "Mondrak, Glory Dominus";
|
||||
Ability ability = new SimpleActivatedAbility(
|
||||
Zone.ALL,
|
||||
new CreateTokenEffect(new FoodToken(), 2),
|
||||
new ManaCostsImpl<>("")
|
||||
);
|
||||
addCustomCardWithAbility("Token-maker", playerA, ability);
|
||||
|
||||
addCard(Zone.BATTLEFIELD, playerA, mondrak);
|
||||
addCard(Zone.BATTLEFIELD, playerA, angelOfJubilation);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bear Cub", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 3);
|
||||
|
||||
checkPlayableAbility("Can't activate Mondrak", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W/P}{W/P}, Sacrifice", false);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "create two");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W/P}{W/P}, Sacrifice");
|
||||
setChoice(playerA, "Food Token", 2);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertCounterCount(playerA, mondrak, CounterType.INDESTRUCTIBLE, 1);
|
||||
assertPermanentCount(playerA, "Bear Cub", 2);
|
||||
assertPermanentCount(playerA, "Food Token", 2);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package org.mage.test.cards.single.dmu;
|
|||
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
|
|
@ -76,4 +75,26 @@ public class KarnsSylexTest extends CardTestPlayerBase {
|
|||
assertLife(playerB, 20 - 3);
|
||||
assertGraveyardCount(playerA, "Lightning Bolt", 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that it does not work with mana abilities, e.g. Thran Portal, with Yasharn, Implacable Earth.
|
||||
*/
|
||||
@Test
|
||||
public void blockedManaAbilitiesWithYasharn() {
|
||||
addCard(Zone.HAND, playerA, "Thran Portal");
|
||||
addCard(Zone.HAND, playerA, "Lightning Bolt");
|
||||
addCard(Zone.BATTLEFIELD, playerA, karnsSylex);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Yasharn, Implacable Earth");
|
||||
|
||||
playLand(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Thran Portal");
|
||||
setChoice(playerA, "Thran");
|
||||
setChoice(playerA, "Mountain");
|
||||
|
||||
checkPlayableAbility("restricted by Yasharn", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Lightning Bolt", false);
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
assertLife(playerA, 20);
|
||||
assertLife(playerB, 20);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import mage.constants.Zone;
|
|||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* {@link mage.cards.t.ThranPortal Thran Portal}
|
||||
* Land Gate
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
package org.mage.test.cards.single.znr;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.common.SimpleActivatedAbility;
|
||||
import mage.abilities.costs.mana.ManaCostsImpl;
|
||||
import mage.abilities.effects.common.CreateTokenEffect;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.counters.CounterType;
|
||||
import mage.game.permanent.token.FoodToken;
|
||||
import mage.game.permanent.token.TreasureToken;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestPlayerBase;
|
||||
|
||||
|
|
@ -23,21 +30,26 @@ public class YasharnImplacableEarthTest extends CardTestPlayerBase {
|
|||
* Test that players can't pay life to cast a spell.
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void cantPayLifeToCast() {
|
||||
// {W}{B}
|
||||
// As an additional cost to cast this spell, pay 5 life or sacrifice a creature or enchantment.
|
||||
// Destroy target creature.
|
||||
addCard(Zone.HAND, playerA, "Final Payment");
|
||||
//{4}{B/P}{B/P}{B/P}
|
||||
//Legendary Creature — Phyrexian Horror Minion
|
||||
//2/2
|
||||
//Lifelink
|
||||
//For each {B} in a cost, you may pay 2 life rather than pay that mana.
|
||||
//Whenever you cast a black spell, put a +1/+1 counter on K'rrik.
|
||||
addCard(Zone.HAND, playerA, "K'rrik, Son of Yawgmoth");
|
||||
addCard(Zone.BATTLEFIELD, playerA, yasharn);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 5);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Final Payment", yasharn);
|
||||
setChoice(playerA, "No");
|
||||
setChoice(playerA, "Silvercoat Lion");
|
||||
checkPlayableAbility("Can't cast Final Payment", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Final Payment", false);
|
||||
checkPlayableAbility("Can't cast K'rrik", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast K'rrik, Son of Yawgmoth", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
|
|
@ -71,22 +83,20 @@ public class YasharnImplacableEarthTest extends CardTestPlayerBase {
|
|||
* Test that players can't sacrifice a nonland permanent to cast a spell.
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void cantSacrificeNonlandToCast() {
|
||||
// {1}{B}
|
||||
// As an additional cost to cast this spell, sacrifice an artifact or creature.
|
||||
// Draw two cards and create a Treasure token.
|
||||
addCard(Zone.HAND, playerA, "Deadly Dispute");
|
||||
addCard(Zone.BATTLEFIELD, playerA, yasharn);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bear Cub");
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
|
||||
|
||||
setStrictChooseMode(true);
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Deadly Dispute");
|
||||
setChoice(playerA, yasharn);
|
||||
checkPlayableAbility("Can't cast Deadly Dispute", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Cast Deadly Dispute", false);
|
||||
|
||||
setStopAt(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
execute();
|
||||
assertPermanentCount(playerA, "Treasure Token", 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -117,9 +127,13 @@ public class YasharnImplacableEarthTest extends CardTestPlayerBase {
|
|||
*/
|
||||
@Test
|
||||
public void canSacrificeLandToCast() {
|
||||
//Crop Rotation
|
||||
//{G}
|
||||
//As an additional cost to cast this spell, sacrifice a land.
|
||||
//Search your library for a land card, put that card onto the battlefield, then shuffle.
|
||||
addCard(Zone.HAND, playerA, "Crop Rotation");
|
||||
addCard(Zone.BATTLEFIELD, playerA, yasharn);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Forest");
|
||||
addCard(Zone.HAND, playerA, "Crop Rotation");
|
||||
addCard(Zone.LIBRARY, playerA, "Mountain");
|
||||
|
||||
setStrictChooseMode(true);
|
||||
|
|
@ -156,4 +170,159 @@ public class YasharnImplacableEarthTest extends CardTestPlayerBase {
|
|||
assertPermanentCount(playerA, "Island", 1);
|
||||
assertGraveyardCount(playerA, "Evolving Wilds", 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a player cannot sacrifice artifacts or creatures to activate abilities
|
||||
*/
|
||||
@Test
|
||||
public void cantSacToMondrakWithArtifacts() {
|
||||
setStrictChooseMode(true);
|
||||
//Mondrak, Glory Dominus
|
||||
//{2}{W}{W}
|
||||
//Legendary Creature — Phyrexian Horror
|
||||
//If one or more tokens would be created under your control, twice that many of those tokens are created instead.
|
||||
//{1}{W/P}{W/P}, Sacrifice two other artifacts and/or creatures: Put an indestructible counter on Mondrak.
|
||||
String mondrak = "Mondrak, Glory Dominus";
|
||||
|
||||
Ability ability = new SimpleActivatedAbility(
|
||||
Zone.ALL,
|
||||
new CreateTokenEffect(new TreasureToken(), 2),
|
||||
new ManaCostsImpl<>("")
|
||||
);
|
||||
ability.addEffect(new CreateTokenEffect(new FoodToken(), 2));
|
||||
|
||||
addCustomCardWithAbility("Token-maker", playerA, ability);
|
||||
addCard(Zone.BATTLEFIELD, playerA, mondrak);
|
||||
addCard(Zone.BATTLEFIELD, playerA, yasharn);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Bear Cub", 2);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Plains", 3);
|
||||
|
||||
checkPlayableAbility("Can't activate Mondrak with creatures", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W/P}{W/P}, Sacrifice", false);
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "create two");
|
||||
waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN);
|
||||
|
||||
checkPlayableAbility("Can't activate Mondrak with creatures or artifacts", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}{W/P}{W/P}, Sacrifice", false);
|
||||
|
||||
setStopAt(1, PhaseStep.BEGIN_COMBAT);
|
||||
execute();
|
||||
|
||||
assertPermanentCount(playerA, "Bear Cub", 2);
|
||||
assertPermanentCount(playerA, "Treasure Token", 4);
|
||||
assertPermanentCount(playerA, "Food Token", 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canSacrificeTriggeredAbility() {
|
||||
/*
|
||||
Unscrupulous Contractor
|
||||
{2}{B}
|
||||
Creature — Human Assassin
|
||||
When this creature enters, you may sacrifice a creature. When you do, target player draws two cards and loses 2 life.
|
||||
Plot {2}{B}
|
||||
3/2
|
||||
*/
|
||||
String contractor = "Unscrupulous Contractor";
|
||||
/*
|
||||
Bear Cub
|
||||
{1}{G}
|
||||
Creature - Bear
|
||||
2/2
|
||||
*/
|
||||
String cub = "Bear Cub";
|
||||
|
||||
setStrictChooseMode(true);
|
||||
addCard(Zone.BATTLEFIELD, playerA, yasharn);
|
||||
addCard(Zone.HAND, playerA, contractor);
|
||||
addCard(Zone.HAND, playerB, contractor);
|
||||
addCard(Zone.BATTLEFIELD, playerA, cub);
|
||||
addCard(Zone.BATTLEFIELD, playerB, cub);
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
|
||||
addCard(Zone.BATTLEFIELD, playerB, "Swamp", 3);
|
||||
|
||||
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, contractor);
|
||||
setChoice(playerA, true);
|
||||
setChoice(playerA, cub);
|
||||
addTarget(playerA, playerA);
|
||||
|
||||
castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, contractor);
|
||||
setChoice(playerB, true);
|
||||
setChoice(playerB, cub);
|
||||
addTarget(playerB, playerB);
|
||||
|
||||
setStopAt(2, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertHandCount(playerA, 2);
|
||||
assertLife(playerA, 20 - 2);
|
||||
assertGraveyardCount(playerA, cub, 1);
|
||||
|
||||
assertHandCount(playerB, 1 + 2); //draw + contractor effect
|
||||
assertLife(playerB, 20 - 2);
|
||||
assertGraveyardCount(playerB, cub, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canPayLifeForTriggeredAbility() {
|
||||
/*
|
||||
Arrogant Poet
|
||||
{1}{B}
|
||||
Creature — Human Warlock
|
||||
Whenever this creature attacks, you may pay 2 life. If you do, it gains flying until end of turn.
|
||||
2/1
|
||||
*/
|
||||
String poet = "Arrogant Poet";
|
||||
setStrictChooseMode(true);
|
||||
addCard(Zone.BATTLEFIELD, playerA, poet);
|
||||
addCard(Zone.BATTLEFIELD, playerA, yasharn);
|
||||
|
||||
attack(1, playerA, poet);
|
||||
setChoice(playerA, true); // pay 2 life
|
||||
|
||||
setStopAt(1, PhaseStep.POSTCOMBAT_MAIN);
|
||||
execute();
|
||||
|
||||
assertLife(playerB, 20 - 2); // combat damage
|
||||
assertLife(playerA, 20 - 2); // paid life
|
||||
assertAbility(playerA, poet, FlyingAbility.getInstance(), true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canSacWithGrist() {
|
||||
/*
|
||||
Grist, the Hunger Tide
|
||||
{1}{B}{G}
|
||||
Legendary Planeswalker — Grist
|
||||
As long as Grist isn’t on the battlefield, it’s a 1/1 Insect creature in addition to its other types.
|
||||
+1: Create a 1/1 black and green Insect creature token, then mill a card. If an Insect card was milled this way, put a loyalty counter on Grist and repeat this process.
|
||||
−2: You may sacrifice a creature. When you do, destroy target creature or planeswalker.
|
||||
−5: Each opponent loses life equal to the number of creature cards in your graveyard.
|
||||
Loyalty: 3
|
||||
*/
|
||||
String grist = "Grist, the Hunger Tide";
|
||||
/*
|
||||
Bear Cub
|
||||
{1}{G}
|
||||
Creature - Bear
|
||||
2/2
|
||||
*/
|
||||
String cub = "Bear Cub";
|
||||
setStrictChooseMode(true);
|
||||
addCard(Zone.BATTLEFIELD, playerA, grist);
|
||||
addCard(Zone.BATTLEFIELD, playerA, yasharn);
|
||||
addCard(Zone.BATTLEFIELD, playerA, cub);
|
||||
addCard(Zone.BATTLEFIELD, playerB, grist + "@gristB");
|
||||
|
||||
activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "-2:");
|
||||
setChoice(playerA, true);
|
||||
setChoice(playerA, cub);
|
||||
addTarget(playerA, "@gristB");
|
||||
|
||||
setStopAt(1, PhaseStep.END_TURN);
|
||||
execute();
|
||||
|
||||
assertCounterCount(grist, CounterType.LOYALTY, 1);
|
||||
assertGraveyardCount(playerB, grist, 1);
|
||||
assertGraveyardCount(playerA, cub, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3760,13 +3760,13 @@ public class TestPlayer implements Player {
|
|||
}
|
||||
|
||||
@Override
|
||||
public PayLifeCostLevel getPayLifeCostLevel() {
|
||||
return computerPlayer.getPayLifeCostLevel();
|
||||
public EnumSet<PayLifeCostRestriction> getPayLifeCostRestrictions() {
|
||||
return computerPlayer.getPayLifeCostRestrictions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPayLifeCostLevel(PayLifeCostLevel payLifeCostLevel) {
|
||||
computerPlayer.setPayLifeCostLevel(payLifeCostLevel);
|
||||
public void addPayLifeCostRestriction(PayLifeCostRestriction payLifeCostRestriction) {
|
||||
computerPlayer.addPayLifeCostRestriction(payLifeCostRestriction);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -3774,16 +3774,6 @@ public class TestPlayer implements Player {
|
|||
return computerPlayer.canPaySacrificeCost(permanent, source, controllerId, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterPermanent getSacrificeCostFilter() {
|
||||
return computerPlayer.getSacrificeCostFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCanPaySacrificeCostFilter(FilterPermanent permanent) {
|
||||
computerPlayer.setCanPaySacrificeCostFilter(permanent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canLoseByZeroOrLessLife() {
|
||||
return computerPlayer.canLoseByZeroOrLessLife();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,151 @@
|
|||
package mage.abilities.common;
|
||||
|
||||
import mage.abilities.Ability;
|
||||
import mage.abilities.effects.ContinuousEffectImpl;
|
||||
import mage.abilities.effects.ContinuousRuleModifyingEffectImpl;
|
||||
import mage.constants.*;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.Game;
|
||||
import mage.game.events.GameEvent;
|
||||
import mage.game.permanent.Permanent;
|
||||
import mage.players.Player;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Effect used to prevent paying life and, optionally, sacrificing permanents as a cost for activated abilities and casting spells.
|
||||
* @author Jmlundeen
|
||||
*/
|
||||
public class CantPayLifeOrSacrificeAbility extends SimpleStaticAbility {
|
||||
|
||||
private final String rule;
|
||||
|
||||
public CantPayLifeOrSacrificeAbility(FilterPermanent sacrificeFilter) {
|
||||
this(false, sacrificeFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onlyNonManaAbilities boolean to set if the restriction should only apply to non-mana abilities
|
||||
* @param sacrificeFilter filter for types of permanents that cannot be sacrificed, can be null if sacrifice not needed.
|
||||
* e.g. Karn's Sylex
|
||||
*/
|
||||
public CantPayLifeOrSacrificeAbility(boolean onlyNonManaAbilities, FilterPermanent sacrificeFilter) {
|
||||
super(new CantPayLifeEffect(onlyNonManaAbilities));
|
||||
if (sacrificeFilter != null) {
|
||||
addEffect(new CantSacrificeEffect(onlyNonManaAbilities, sacrificeFilter));
|
||||
}
|
||||
this.rule = makeRule(onlyNonManaAbilities, sacrificeFilter);
|
||||
}
|
||||
|
||||
private CantPayLifeOrSacrificeAbility(CantPayLifeOrSacrificeAbility effect) {
|
||||
super(effect);
|
||||
this.rule = effect.rule;
|
||||
}
|
||||
|
||||
public CantPayLifeOrSacrificeAbility copy() {
|
||||
return new CantPayLifeOrSacrificeAbility(this);
|
||||
}
|
||||
|
||||
String makeRule(boolean nonManaAbilities, FilterPermanent sacrificeFilter) {
|
||||
StringBuilder sb = new StringBuilder("Players can't pay life");
|
||||
if (sacrificeFilter != null) {
|
||||
sb.append(" or sacrifice ").append(sacrificeFilter.getMessage());
|
||||
}
|
||||
sb.append(" to cast spells or activate abilities");
|
||||
if (nonManaAbilities) {
|
||||
sb.append(" that aren't mana abilities");
|
||||
}
|
||||
sb.append(".");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRule() {
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
|
||||
class CantPayLifeEffect extends ContinuousEffectImpl {
|
||||
|
||||
private final boolean onlyNonManaAbilities;
|
||||
|
||||
/**
|
||||
* @param onlyNonManaAbilities boolean to set if the restriction should only apply to non-mana abilities
|
||||
*/
|
||||
CantPayLifeEffect(boolean onlyNonManaAbilities) {
|
||||
super(Duration.WhileOnBattlefield, Layer.PlayerEffects, SubLayer.NA, Outcome.Detriment);
|
||||
this.onlyNonManaAbilities = onlyNonManaAbilities;
|
||||
}
|
||||
|
||||
private CantPayLifeEffect(CantPayLifeEffect effect) {
|
||||
super(effect);
|
||||
this.onlyNonManaAbilities = effect.onlyNonManaAbilities;
|
||||
}
|
||||
|
||||
public CantPayLifeEffect copy() {
|
||||
return new CantPayLifeEffect(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean apply(Game game, Ability source) {
|
||||
for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) {
|
||||
Player player = game.getPlayer(playerId);
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
player.addPayLifeCostRestriction(Player.PayLifeCostRestriction.CAST_SPELLS);
|
||||
if (this.onlyNonManaAbilities) {
|
||||
player.addPayLifeCostRestriction(Player.PayLifeCostRestriction.ACTIVATE_NON_MANA_ABILITIES);
|
||||
} else {
|
||||
player.addPayLifeCostRestriction(Player.PayLifeCostRestriction.ACTIVATE_MANA_ABILITIES);
|
||||
player.addPayLifeCostRestriction(Player.PayLifeCostRestriction.ACTIVATE_NON_MANA_ABILITIES);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class CantSacrificeEffect extends ContinuousRuleModifyingEffectImpl {
|
||||
|
||||
private final FilterPermanent sacrificeFilter;
|
||||
private final boolean onlyNonManaAbilities;
|
||||
|
||||
CantSacrificeEffect(boolean onlyNonManaAbilities, FilterPermanent sacrificeFilter) {
|
||||
super(Duration.WhileOnBattlefield, Outcome.Detriment);
|
||||
this.sacrificeFilter = sacrificeFilter;
|
||||
this.onlyNonManaAbilities = onlyNonManaAbilities;
|
||||
}
|
||||
|
||||
private CantSacrificeEffect(CantSacrificeEffect effect) {
|
||||
super(effect);
|
||||
this.sacrificeFilter = effect.sacrificeFilter.copy();
|
||||
this.onlyNonManaAbilities = effect.onlyNonManaAbilities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checksEventType(GameEvent event, Game game) {
|
||||
return event.getType() == GameEvent.EventType.PAY_SACRIFICE_COST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applies(GameEvent event, Ability source, Game game) {
|
||||
Permanent permanent = game.getPermanent(event.getTargetId());
|
||||
Optional<Ability> abilityOptional = game.getAbility(UUID.fromString(event.getData()), event.getSourceId());
|
||||
if (permanent == null || !abilityOptional.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
Ability abilityWithCost = abilityOptional.get();
|
||||
boolean isActivatedAbility = (onlyNonManaAbilities && abilityWithCost.isManaActivatedAbility()) ||
|
||||
(!onlyNonManaAbilities && abilityWithCost.isActivatedAbility());
|
||||
if (!isActivatedAbility && abilityWithCost.getAbilityType() != AbilityType.SPELL) {
|
||||
return false;
|
||||
}
|
||||
return this.sacrificeFilter.match(permanent, event.getPlayerId(), source, game);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CantSacrificeEffect copy() {
|
||||
return new CantSacrificeEffect(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -688,6 +688,13 @@ public class GameEvent implements Serializable {
|
|||
/* rad counter life loss/gain effect
|
||||
*/
|
||||
RADIATION_GAIN_LIFE,
|
||||
/* for checking sacrifice as a cost
|
||||
targetId the permanent to be sacrificed
|
||||
sourceId of the ability
|
||||
playerId controller of ability
|
||||
data id of the ability being paid for
|
||||
*/
|
||||
PAY_SACRIFICE_COST,
|
||||
// custom events - must store some unique data to track
|
||||
CUSTOM_EVENT;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import mage.designations.Designation;
|
|||
import mage.designations.DesignationType;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.FilterMana;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.game.*;
|
||||
import mage.game.draft.Draft;
|
||||
import mage.game.events.GameEvent;
|
||||
|
|
@ -37,10 +36,7 @@ import mage.util.Copyable;
|
|||
import mage.util.MultiAmountMessage;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
|
@ -57,16 +53,13 @@ import java.util.stream.Collectors;
|
|||
public interface Player extends MageItem, Copyable<Player> {
|
||||
|
||||
/**
|
||||
* Enum used to indicate what each player is allowed to spend life on.
|
||||
* By default it is set to `allAbilities`, but can be changed by effects.
|
||||
* E.g. Angel of Jubilation sets it to `nonSpellnonActivatedAbilities`,
|
||||
* and Karn's Sylex sets it to `onlyManaAbilities`.
|
||||
* <p>
|
||||
* <p>
|
||||
* Default is PayLifeCostLevel.allAbilities.
|
||||
* Enum used to indicate what each player is not allowed to spend life on.
|
||||
* By default a player has no restrictions, but can be changed by effects.
|
||||
* E.g. Angel of Jubilation adds `CAST_SPELLS` and 'ACTIVATE_ABILITIES',
|
||||
* and Karn's Sylex adds `CAST_SPELLS` and 'ACTIVATE_NON_MANA_ABILITIES'.
|
||||
*/
|
||||
enum PayLifeCostLevel {
|
||||
allAbilities, nonSpellnonActivatedAbilities, onlyManaAbilities, none
|
||||
enum PayLifeCostRestriction {
|
||||
CAST_SPELLS, ACTIVATE_NON_MANA_ABILITIES, ACTIVATE_MANA_ABILITIES
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -177,14 +170,14 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
boolean isCanGainLife();
|
||||
|
||||
/**
|
||||
* Is the player allowed to pay life for casting spells or activate activated abilities
|
||||
* Adds a {@link PayLifeCostRestriction} to the set of restrictions.
|
||||
*
|
||||
* @param payLifeCostLevel
|
||||
* @param payLifeCostRestriction
|
||||
*/
|
||||
|
||||
void setPayLifeCostLevel(PayLifeCostLevel payLifeCostLevel);
|
||||
void addPayLifeCostRestriction(PayLifeCostRestriction payLifeCostRestriction);
|
||||
|
||||
PayLifeCostLevel getPayLifeCostLevel();
|
||||
EnumSet<PayLifeCostRestriction> getPayLifeCostRestrictions();
|
||||
|
||||
/**
|
||||
* Can the player pay life to cast or activate the given ability
|
||||
|
|
@ -194,10 +187,6 @@ public interface Player extends MageItem, Copyable<Player> {
|
|||
*/
|
||||
boolean canPayLifeCost(Ability Ability);
|
||||
|
||||
void setCanPaySacrificeCostFilter(FilterPermanent filter);
|
||||
|
||||
FilterPermanent getSacrificeCostFilter();
|
||||
|
||||
boolean canPaySacrificeCost(Permanent permanent, Ability source, UUID controllerId, Game game);
|
||||
|
||||
void setLifeTotalCanChange(boolean lifeTotalCanChange);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import mage.designations.DesignationType;
|
|||
import mage.designations.Speed;
|
||||
import mage.filter.FilterCard;
|
||||
import mage.filter.FilterMana;
|
||||
import mage.filter.FilterPermanent;
|
||||
import mage.filter.StaticFilters;
|
||||
import mage.filter.common.FilterControlledPermanent;
|
||||
import mage.filter.common.FilterCreatureForCombat;
|
||||
|
|
@ -150,14 +149,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
protected boolean isFastFailInTestMode = true;
|
||||
protected boolean canGainLife = true;
|
||||
protected boolean canLoseLife = true;
|
||||
protected PayLifeCostLevel payLifeCostLevel = PayLifeCostLevel.allAbilities;
|
||||
protected EnumSet<PayLifeCostRestriction> payLifeCostRestrictions = EnumSet.noneOf(PayLifeCostRestriction.class);
|
||||
protected boolean loseByZeroOrLessLife = true;
|
||||
protected boolean canPlotFromTopOfLibrary = false;
|
||||
protected boolean drawsFromBottom = false;
|
||||
protected boolean drawsOnOpponentsTurn = false;
|
||||
protected int speed = 0;
|
||||
|
||||
protected FilterPermanent sacrificeCostFilter;
|
||||
protected List<AlternativeSourceCosts> alternativeSourceCosts = new ArrayList<>();
|
||||
|
||||
// TODO: rework turn controller to use single list (see other todos)
|
||||
|
|
@ -264,8 +262,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.userData = player.userData;
|
||||
this.matchPlayer = player.matchPlayer;
|
||||
|
||||
this.payLifeCostLevel = player.payLifeCostLevel;
|
||||
this.sacrificeCostFilter = player.sacrificeCostFilter;
|
||||
this.payLifeCostRestrictions = player.payLifeCostRestrictions;
|
||||
this.alternativeSourceCosts = CardUtil.deepCopyObject(player.alternativeSourceCosts);
|
||||
this.storedBookmark = player.storedBookmark;
|
||||
|
||||
|
|
@ -364,9 +361,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
|
||||
this.inRange.clear();
|
||||
this.inRange.addAll(((PlayerImpl) player).inRange);
|
||||
this.payLifeCostLevel = player.getPayLifeCostLevel();
|
||||
this.sacrificeCostFilter = player.getSacrificeCostFilter() != null
|
||||
? player.getSacrificeCostFilter().copy() : null;
|
||||
this.payLifeCostRestrictions = player.getPayLifeCostRestrictions();
|
||||
this.loseByZeroOrLessLife = player.canLoseByZeroOrLessLife();
|
||||
this.canPlotFromTopOfLibrary = player.canPlotFromTopOfLibrary();
|
||||
this.drawsFromBottom = player.isDrawsFromBottom();
|
||||
|
|
@ -481,14 +476,13 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
//this.isTestMode // must keep
|
||||
this.canGainLife = true;
|
||||
this.canLoseLife = true;
|
||||
this.payLifeCostLevel = PayLifeCostLevel.allAbilities;
|
||||
this.payLifeCostRestrictions.clear();
|
||||
this.loseByZeroOrLessLife = true;
|
||||
this.canPlotFromTopOfLibrary = false;
|
||||
this.drawsFromBottom = false;
|
||||
this.drawsOnOpponentsTurn = false;
|
||||
this.speed = 0;
|
||||
|
||||
this.sacrificeCostFilter = null;
|
||||
this.alternativeSourceCosts.clear();
|
||||
|
||||
this.isGameUnderControl = true;
|
||||
|
|
@ -524,8 +518,7 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
this.maxAttackedBy = Integer.MAX_VALUE;
|
||||
this.canGainLife = true;
|
||||
this.canLoseLife = true;
|
||||
this.payLifeCostLevel = PayLifeCostLevel.allAbilities;
|
||||
this.sacrificeCostFilter = null;
|
||||
this.payLifeCostRestrictions.clear();
|
||||
this.loseByZeroOrLessLife = true;
|
||||
this.canPlotFromTopOfLibrary = false;
|
||||
this.drawsFromBottom = false;
|
||||
|
|
@ -4683,46 +4676,41 @@ public abstract class PlayerImpl implements Player, Serializable {
|
|||
return false;
|
||||
}
|
||||
|
||||
switch (payLifeCostLevel) {
|
||||
case allAbilities:
|
||||
return true;
|
||||
case onlyManaAbilities:
|
||||
return ability.isManaAbility();
|
||||
case nonSpellnonActivatedAbilities:
|
||||
return !ability.getAbilityType().isActivatedAbility()
|
||||
&& ability.getAbilityType() != AbilityType.SPELL;
|
||||
case none:
|
||||
default:
|
||||
return false;
|
||||
boolean canPay = true;
|
||||
for (PayLifeCostRestriction restriction : payLifeCostRestrictions) {
|
||||
switch (restriction) {
|
||||
case CAST_SPELLS:
|
||||
canPay &= ability.getAbilityType() != AbilityType.SPELL;
|
||||
break;
|
||||
case ACTIVATE_NON_MANA_ABILITIES:
|
||||
canPay &= !ability.isNonManaActivatedAbility();
|
||||
break;
|
||||
case ACTIVATE_MANA_ABILITIES:
|
||||
canPay &= !ability.isManaActivatedAbility();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return canPay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayLifeCostLevel getPayLifeCostLevel() {
|
||||
return payLifeCostLevel;
|
||||
public EnumSet<PayLifeCostRestriction> getPayLifeCostRestrictions() {
|
||||
return payLifeCostRestrictions;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setPayLifeCostLevel(PayLifeCostLevel payLifeCostLevel) {
|
||||
this.payLifeCostLevel = payLifeCostLevel;
|
||||
public void addPayLifeCostRestriction(PayLifeCostRestriction payLifeCostRestriction) {
|
||||
this.payLifeCostRestrictions.add(payLifeCostRestriction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPaySacrificeCost(Permanent permanent, Ability source, UUID controllerId, Game game) {
|
||||
return permanent.canBeSacrificed() &&
|
||||
(sacrificeCostFilter == null || !sacrificeCostFilter.match(permanent, controllerId, source, game));
|
||||
if (!permanent.canBeSacrificed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCanPaySacrificeCostFilter(FilterPermanent filter
|
||||
) {
|
||||
this.sacrificeCostFilter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilterPermanent getSacrificeCostFilter() {
|
||||
return sacrificeCostFilter;
|
||||
String sourceIdString = source.getId().toString();
|
||||
return !(game.replaceEvent(GameEvent.getEvent(GameEvent.EventType.PAY_SACRIFICE_COST, permanent.getId(), source, controllerId, sourceIdString, 1)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue